Tech Off Thread

14 posts

I always get a black backgraound with DrawingContext in WPF

Back to Forum: Tech Off
  • User profile image
    Dioniz

    I am newbie to wpf and to this sort of programming in general. I have an example from Sams' WPF 4 Unleashed to which I am unable to set the background. I show it here greatly simplified and in VB.NET:

    Imports System.Windows
    Imports System.Windows.Media
    Imports System.Windows.Input
    
    Namespace WTH
       Class Application
          <STAThread()> _
          Public Shared Sub Main()
             Dim whv As New WTH.WindowHostingVisual
             whv.ShowDialog()
          End Sub
       End Class
    
       Public Class WindowHostingVisual
          Inherits Window
    
          Private visuals As New List(Of Visual)()
    
          Public Sub New()
             Title = "Hosting DrawingVisuals"
             Width = 400
             Height = 400
             WindowStartupLocation = Windows.WindowStartupLocation.CenterScreen
             Background = Brushes.Aquamarine
             Dim visLine As New DrawingVisual()
             Using dc As DrawingContext = visLine.RenderOpen()
                dc.DrawLine(New Pen(Brushes.White, 5), New Point(50, 200), New Point(350, 200))
                dc.PushOpacity(0.5)
             End Using
             visuals.Add(visLine)
          End Sub
    
          Protected Overrides ReadOnly Property VisualChildrenCount() As Integer
             Get
                Return visuals.Count
             End Get
          End Property
    
          Protected Overrides Function GetVisualChild(index As Integer) As Visual
             If index < 0 OrElse index >= visuals.Count Then
                Throw New ArgumentOutOfRangeException("index")
             End If
             Return visuals(index)
          End Function
    
       End Class
    End Namespace

    Using XAML is what I want to avoid. Otherwise, what's the point of programming if I can't change things when and as I want. When I use the standard files created Automatically by VS 2010 Express for WPF, I can change the background just fine through the Window or the Grid. Here is the empty XAML version I am using now:

    <Application x:Class="Application"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
        <Application.Resources>
            
        </Application.Resources>
    </Application>

    When I comment out the two overrides in my code, the background changes, but nothing is drawn.

    I have tried everything, so please save me from suicide.

  • User profile image
    Dexter

    The background is not magical, it is drawn by the window. If you replace the visual content of the windows like this, there will be no background unless you draw it yourself.

    You could add something like this

    dc.DrawRectangle(Background, Nothing, New Rect(0, 0, Width, Height))

    do your drawing code. 

  • User profile image
    Dioniz

    Whew! Thanks dex. Actually I wasn't going to do anything drastic.

     I put the background on first to give it the lowest Z order and then pass over it in the hit testing. Here is the complete code for the sake of public documentation; it draw some scary ghost:

    Imports System.Windows
    Imports System.Windows.Media
    Imports System.Collections.Generic
    Imports System.Windows.Input
    
    Namespace WindowHostingVisual
       Class Application
          <STAThread()> _
          Public Shared Sub Main()
             Dim whv As New WindowHostingVisual.WindowHostingVisual()
             whv.ShowDialog()
          End Sub
       End Class
    
       Namespace WindowHostingVisual
          Public Class WindowHostingVisual
             Inherits Window
    
             Private visuals As New List(Of Visual)()
    
             Public Sub New()
                Title = "Hosting DrawingVisuals"
                Width = 400
                Height = 400
                Background = Brushes.Aquamarine
                Me.WindowStartupLocation = Windows.WindowStartupLocation.CenterScreen
    
                Dim bodyVisual As New DrawingVisual()
                Dim eyesVisual As New DrawingVisual()
                Dim mouthVisual As New DrawingVisual()
                Dim visBackground As New DrawingVisual
    
                Using dc As DrawingContext = bodyVisual.RenderOpen()
                   dc.DrawGeometry(Brushes.Blue, Nothing, Geometry.Parse("M 240,250 C 200,375 200,250 175,200 C 100,400 100,250 100,200 C 0,350 0,250 30,130 C 75,0 100,0 150,0 C 200,0 250,0 250,150 Z"))
                End Using
    
                Using dc As DrawingContext = eyesVisual.RenderOpen()
                   dc.DrawEllipse(Brushes.Black, New Pen(Brushes.White, 10), New Point(95, 95), 15, 15)
                   dc.DrawEllipse(Brushes.Black, New Pen(Brushes.White, 10), New Point(170, 105), 15, 15)
                End Using
    
                Using dc As DrawingContext = mouthVisual.RenderOpen()
                   Dim p As New Pen(Brushes.Black, 10)
                   p.StartLineCap = PenLineCap.Round
                   p.EndLineCap = PenLineCap.Round
                   dc.DrawLine(p, New Point(75, 160), New Point(175, 150))
                End Using
    
                Using dc As DrawingContext = visBackground.RenderOpen
                   dc.DrawRectangle(Background, Nothing, New Rect(0, 0, Width, Height))
                End Using
    
                visuals.Add(visBackground)
                visuals.Add(bodyVisual)
                visuals.Add(eyesVisual)
                visuals.Add(mouthVisual)
    
                ' Bookkeeping:
                For Each v As Visual In visuals
                   AddVisualChild(v)
                   AddLogicalChild(v)
                Next
    
             End Sub
    
             ' The two necessary overrides, implemented for the single Visual:
             Protected Overrides ReadOnly Property VisualChildrenCount() As Integer
                Get
                   Return visuals.Count
                End Get
             End Property
    
             Protected Overrides Function GetVisualChild(index As Integer) As Visual
                If index < 0 OrElse index >= visuals.Count Then
                   Throw New ArgumentOutOfRangeException("index")
                End If
                Return visuals(index)
             End Function
    
             Protected Overrides Sub OnMouseLeftButtonDown(e As MouseButtonEventArgs)
                ' Rotate object clicked on.
    
                MyBase.OnMouseLeftButtonDown(e)
                ' Retrieve the mouse pointer location relative to the Window
                Dim location As Point = e.GetPosition(Me)
                ' Perform visual hit testing
                Dim result As HitTestResult = VisualTreeHelper.HitTest(Me, location)
                ' If we hit any DrawingVisual, rotate it
                If result.VisualHit.[GetType]() = GetType(DrawingVisual) Then
                   Dim dv As DrawingVisual = TryCast(result.VisualHit, DrawingVisual)
                   ' Don't rotate background.
                   If dv.Equals(visuals.Item(0)) Then
                      Exit Sub
                   End If
                   If dv.Transform Is Nothing Then
                      dv.Transform = New RotateTransform()
                   End If
                   TryCast(dv.Transform, RotateTransform).Angle += 1
                End If
             End Sub
          End Class
       End Namespace
    End Namespace
    

    I think my real question is what I am really drawing on. It seems to not be the window object. I don't know what the DrawingContext actually is and where it fits in. I do now know the drawn objects can be accessed through LogicalTreeHelper and VisualTreeHelper. To quote from WPF Unleashed: "However, you can easily traverse both the logical and visual trees using the somewhat
    symmetrical System.Windows.LogicalTreeHelper and System.Windows.Media.
    VisualTreeHelper classes."

  • User profile image
    Dexter

    I think my real question is what I am really drawing on. It seems to not be the window object. I don't know what the DrawingContext actually is and where it fits in

    Well, yes, you're not drawing on the window but on those drawing visuals and they are the children (in the visual tree) of the window. DrawingContext stores the drawing commands and then it is closed (by means of using in this case) the stored commands become the content of the visual. Note that these drawing commands are not part of either the visual or logical tree.

    It's also possible to draw directly on the window in the OnRender method but you need to set the window's template to an empty ControlTemplate, otherwise the template will show on top of your drawing and you won't see anything.

  • User profile image
    Dioniz

    I'm learning a lot, My program now toggles between full screen and normal. The background dimensions are set like this:

     dc.DrawRectangle(Brushes.White, Nothing, New Rect(0, 0, System.Windows.SystemParameters.FullPrimaryScreenWidth, System.Windows.SystemParameters.FullPrimaryScreenHeight))

    so the background always appears to completely cover the window.

    But have a new question related to this. I want to be able to remove an object clicked on, or, change its position. I want to move it without having to remove it and redraw. I also need to know some positioning properties of the visual: x, y,height, width. Using dv in the key event: - dv.Offset.X and Y- just give me zeros, not the relative position. Google isn't helping me much here.

  • User profile image
    Dexter

    Using dv in the key event: - dv.Offset.X and Y- just give me zeros, not the relative position

    Have you tried actually setting the offset? Initially the offset is 0 so no wonder you get zeroes...

  • User profile image
    Dioniz

    Thanks. This is progress. I can now move things around after clicking on them It is a bit odd that I can move an element by setting its Offset but it doesn't know what offset originally was. My code now sets the Offset property of the DrawingVisual when  created and reads it when clicked on. (BTW, the property has to be set by giving it "New Vector(10, 10)" or with whatever values. Otherwise you get "expression is a value and therefore cannot be the target of an assignment" when setting its x and y, even though they are read/write. Forgive for being confused.)

    Again, though, I may have not asked the right question. If I click on a rectangle, is there a way to get the 'System.Windows.Media.RectangleGeometry' out of the DrawingVisual? Can reach its Rect and pen or brush without removing them and recreating them? And I still haven't figured out how to remove objects.

    DrawingContext can draw a Ellipse, Geometry, GlyphRun, Line, and Rectangle, by using DrawDrawing, DrawEllipse, etc. This question goes for all of these.

  • User profile image
    Dexter

    I can move an element by setting its Offset but it doesn't know what offset originally was

    Of course it know, the original offset is 0. You haven't set the offset when you created the visuals, why would it be anything else?

    If I click on a rectangle, is there a way to get the 'System.Windows.Media.RectangleGeometry' out of the DrawingVisual?

    Sort of. A DrawingVisual has a Drawing property of type DrawingGroup. This has a Children property and you can go through all the children and on children of type GeometryDrawing you have a Geometry property. The Geometry object has a number of methods that can be used for hit testing.

    Can reach its Rect and pen or brush without removing them and recreating them?

    Nope, those are read only. But you can do things a bit differently. Start by creating the needed geometries, for example 2 EllipseGeometry. Then draw them on the drawing context by using DrawGeometry instead of DrawEllipse. Now you can modify the properties of those geometries, for example you can apply a transform to them.

    And I still haven't figured out how to remove objects.

    Nope but the above "tweak" applies here to. You can start by creating the necessary geometries, put them all in a DrawingGroup object and the draw the drawing group on the drawing context by using DrawDrawing. Something like this:

    Dim s As New DrawingVisual()
    
    Dim rg As New RectangleGeometry(new Rect(0, 0, 100, 100))
    Dim rgd As New GeometryDrawing(Brushes.Pink, Nothing, rg)
    
    Dim dg As New DrawingGroup()
    dg.Children.Add(rgd)
    
    Using dc As DrawingContext = s.RenderOpen()
        dc.DrawDrawing(dg)
    
    rgd.Brush = Brushes.Yellow
    rg.Transform = new RotateTransform(42)
    dg.Children.Remove(rgd)
    

    But now the question raises, why don't you simply use a Canvas and some shapes?

  • User profile image
    AndyC

    MSDN has pretty extensive documentation on using DrawingVisual, including hit testing:

    http://msdn.microsoft.com/en-us/library/ms742254.aspx#Y2113

  • User profile image
    Dexter

    That sample hit tests visuals as a whole, it doesn't return the geometry inside the visual. Perhaps this is a more appropiate sample: http://msdn.microsoft.com/en-us/library/ms750584.aspx

  • User profile image
    Dioniz

    But now the question raises, why don't you simply use a Canvas and some shapes?

    I should have said at the beginning. Shapes are said to be slower than visuals and I will have up to thousands of little object, so speed is important.

    I didn't have time today to try your suggestions. This problem seems rather burning. Google searches are bringing next to nothing, and are including this thread already. Someone solved it by drawing the background on top of his object, a cop-out if there ever was one, but maybe the only solution. If I get this solved, I'll post the whole solution for others to follow. It'd sure be nice of someone from Microsoft chimed in about now, since this is their site.

    Are EllipseGeometry, RectangleGeometry, and the like shapes or drawingvisuals? And what does it matter if I use a canvas or not? Sorry if these seem like dumb questions.

  • User profile image
    AndyC

    , Acamapichtli wrote

    *snip*

    I should have said at the beginning. Shapes are said to be slower than visuals and I will have up to thousands of little object, so speed is important.

    They're slower because they support all the necessary functionality for eventing and easily modifying them, for moving or changing individual shapes etc. If you implement all that on top of Visuals for your apps needs, you'll likely end up with something even slower (since it won't have all the low-level optimisations that WPF can provide).

  • User profile image
    Dexter

    Are EllipseGeometry, RectangleGeometry, and the like shapes or drawingvisuals?

    Geometries are basically drawing commands which are executed by visuals, they aren't visuals themselfs. Shapes are visuals that contain only one geometry.

    And what does it matter if I use a canvas or not?

    Well, I'd say a canvas is simpler to use. And as AndyC said already, since you have to reimplement a number of things like hit testing, how do you know if your approach will be faster in the end? A quick test I did shows the following:

    • 1000 ellipses - both approaches render fast, the window resizes smoothly
    • 10000 ellipses - things starts to be slugish... in both cases
    • 100000 ellipses - ok, this means trouble... again, in both cases. Some profiling shows that >70% of CPU time is spent in milcore and the video driver, both are native components, they have no idea that you used a canvas or not.

     

  • User profile image
    Dioniz

    I SOLVED IT !!! here is the event handler:

       Protected Overrides Sub OnMouseLeftButtonDown(e As MouseButtonEventArgs)
          MyBase.OnMouseLeftButtonDown(e)
          Dim location As Point = e.GetPosition(Me)
          Dim result As HitTestResult = VisualTreeHelper.HitTest(Me, location)
          Dim hitVisual As DrawingVisual = TryCast(result.VisualHit, DrawingVisual)
          If result.VisualHit.[GetType]() = GetType(DrawingVisual) And Not hitVisual.Equals(visBackground) Then
             visuals.Remove(hitVisual)
             RemoveVisualChild(hitVisual)
             RemoveLogicalChild(hitVisual)
          End If
       End Sub

    Duh: RemoveVisualChild and RemoveLogicalChild. I test for the background to prevent it being deleted. If I want to change the color or other feature of the object, I can delete it and draw again. I don't think the deletion could be noticed. I am working on a generalized example to post. Thanks for helping me on this. Dexter, I'm glad you did that test. Ill have to play around more with this.

Comments closed

Comments have been closed since this content was published more than 30 days ago, but if you'd like to continue the conversation, please create a new thread in our Forums, or Contact Us and let us know.