none
Fast Rendering Leveraging WPF in WinForms RRS feed

  • General discussion

  • It has been a long time coming, but thanks to improvements in the online documentation for WPF interop with WinForms, I've managed to come up with what I believe can serve as a robust rendering and/or game engine for Windows Forms by leveraging some WPF components.

    These initial test are very promising. I'm currently only doing 2D bitmap graphics, but 2D vector graphics will be easy enough to add and because of the WPF capabilities being leveraged, 3D content is possible as well.

    It even looks like I can incorporate the 2D physics engine I have been piddling with on and off.

    I've made a number of attempts at creating an easy-to-use and performant rendering/game engine for WinForms over the years and this one may be the most promising of them all.  To be fair, I haven't stress tested it yet, but the results are promising enough to get excited about.

    In order to use the following example code, you'll need to add the following references to a standard Windows Forms project:

    • WindowsBase
    • WindowsFormsIntegration
    • PresentationCore
    • PresentationFramework

    Those are the primary DLLs necessary for leveraging WPF functionality.

    With these assemblies referenced, you can then begin to create the supporting code.

    The first class we'll need is a simple implementation of a WPF FrameworkElement class.  Think of this somewhat like the top-level WPF user control in the project.  This class' only purpose is to hold the collection of WPF DrawingVisual objects which will represent the Sprite objects in this framework.

    Add the following class to the project:

    'the sprite canvas is a boilerplate implementation of a simple FrameworkElement which provides
    ' user interaction for the sprite (DrawingVisual) objects it contains
    Public Class SpriteCanvas
        Inherits Windows.FrameworkElement
    
        Public ReadOnly Property Children As New Windows.Media.VisualCollection(Me)
    
        Protected Overrides ReadOnly Property VisualChildrenCount() As Integer
            Get
                Return _Children.Count
            End Get
        End Property
    
        Protected Overrides Function GetVisualChild(ByVal index As Integer) As Windows.Media.Visual
            If index < 0 OrElse index >= _Children.Count Then Throw New ArgumentOutOfRangeException()
            Return _Children(index)
        End Function
    End Class

    With this boilerplate class in place we can begin to create the classes more specific to this API.

    The first of these classes is a SpriteSheet class which we will use to load a bitmap image into a WPF BitmapFrame object.  The BitmapFrame represents a single image out of a bitmap file.  Most bitmap files (bmp, png, jpg) contain only a single frame image, but some (gif, tiff) can contain multiple frames.  This example will just handle bmp, png, and jpg files, but the others could be supported.

    Public Class SpriteSheet
    
        'a bitmapframe is a single image from within an image file; most image files only have a single frame,
        ' GIF and TIFF files may contains multiple frames - do not confuse with animation frames created in this class
        Public ReadOnly Property Image As Windows.Media.Imaging.BitmapFrame
    
        Public Sub New(imageFilePath As String)
            Select Case IO.Path.GetExtension(imageFilePath)
                Case ".bmp"
                    Dim decoder As Windows.Media.Imaging.BmpBitmapDecoder = New Windows.Media.Imaging.BmpBitmapDecoder(New Uri(imageFilePath), Windows.Media.Imaging.BitmapCreateOptions.PreservePixelFormat, Windows.Media.Imaging.BitmapCacheOption.OnLoad)
                    Image = decoder.Frames(0)
                Case ".jpg"
                    Dim decoder As Windows.Media.Imaging.JpegBitmapDecoder = New Windows.Media.Imaging.JpegBitmapDecoder(New Uri(imageFilePath), Windows.Media.Imaging.BitmapCreateOptions.PreservePixelFormat, Windows.Media.Imaging.BitmapCacheOption.OnLoad)
                    Image = decoder.Frames(0)
                Case ".png"
                    Dim decoder As Windows.Media.Imaging.PngBitmapDecoder = New Windows.Media.Imaging.PngBitmapDecoder(New Uri(imageFilePath), Windows.Media.Imaging.BitmapCreateOptions.PreservePixelFormat, Windows.Media.Imaging.BitmapCacheOption.OnLoad)
                    Image = decoder.Frames(0)
                Case Else
                    'there are more supported types such as .gif and .tif which could be implemented
                    Throw New ArgumentException("Unsupported image data type.")
            End Select
        End Sub
    
        'this routine is only for full tiled sprite sheets with frames on one axis and animations on the other
        'TODO: replace/supplement with more robust set of animation loading tools
        Public Function GetAnimationFrames(frameSize As Windows.Size, framesHorizontal As Boolean, animationIndex As Integer, flip As SpriteFlipDirection) As IEnumerable(Of Windows.Media.Imaging.TransformedBitmap)
            Dim imageSize As New Windows.Size(Image.PixelWidth, Image.PixelHeight)
            Dim result As New List(Of Windows.Media.Imaging.TransformedBitmap)
            If framesHorizontal Then
                Dim y As Integer = animationIndex * frameSize.Height
                For x = 0 To imageSize.Width - frameSize.Width Step frameSize.Width
                    result.Add(GetSpriteImage(New Windows.Int32Rect(x, y, frameSize.Width, frameSize.Height), flip))
                Next
            Else
                Dim x As Integer = animationIndex * frameSize.Width
                For y = 0 To imageSize.Height - frameSize.Height Step frameSize.Height
                    result.Add(GetSpriteImage(New Windows.Int32Rect(x, y, frameSize.Width, frameSize.Height), flip))
                Next
            End If
            result.Insert(2, result(0).Clone)
            Return result
        End Function
    
        'this routine gets a single sprite image from anywhere within a sprite sheet
        Public Function GetSpriteImage(sourceBounds As Windows.Int32Rect, flip As SpriteFlipDirection) As Windows.Media.Imaging.TransformedBitmap
            Dim scale As New Windows.Media.ScaleTransform
            scale.CenterX = sourceBounds.Width / 2
            scale.CenterY = sourceBounds.Height / 2
            If flip.HasFlag(SpriteFlipDirection.Horizontal) Then scale.ScaleX = -1
            If flip.HasFlag(SpriteFlipDirection.Vertical) Then scale.ScaleY = -1
            Return New Windows.Media.Imaging.TransformedBitmap(New Windows.Media.Imaging.CroppedBitmap(Image, sourceBounds), scale)
        End Function
    End Class
    
    <Flags>
    Public Enum SpriteFlipDirection
        None = 0
        Horizontal = 1
        Vertical = 2
        HorizontalAndVertical = Horizontal Or Vertical
    End Enum

    Upon the creation the SpriteSheet loads the appropriate image file.  It then offers methods to get a particular animation image frame from the sprite sheet or load a series of frames for a complete animation.  Note that this initial animation loading routine is limited to spritesheets laid out in a particular format.  Here is the sheet being used in the example:

    The SpriteFlipDirection enum is provided so that animation frames can be flipped horizontally or vertically as needed.

    With the SpriteSheet in place we can create the Sprite class itself.  This class derives from the WPF DrawingVisual class as that is the type expected by the SpriteCanvas class we created previously.  The DrawingVisual is a lower-level visual object in WPF which doesn't provide any extra functionality such as layout or user interaction.  We'll get those features from the SpriteCanvas.  Our sprite just needs to provide a WPF ImageDrawing object to hold the image to draw, along with the rendering transforms used to draw it, the various animation frames it can display, and a few other variables for controlling the animation and transform effects.

    'the sprite extends DrawingVisual, a WPF object which contains instructions for rendering other visual content
    Public Class Sprite
        Inherits Windows.Media.DrawingVisual
    
        'an internal ImageDrawing WPF object contains the instructions for generating the actual visual content displayed
        Private image As New Windows.Media.ImageDrawing
        'named collections of CroppedBitmap instances represent the frames of each animation used by a sprite
        Private animationFrames As New Dictionary(Of String, IEnumerable(Of Windows.Media.Imaging.TransformedBitmap))
        'local animation variables 
        Private lastRender As TimeSpan
        Private frameTime As Double
        Private frameIndex As Integer
        Private isStillFrame As Boolean
    
        'property to control animation
        Public Property AnimationActive As Boolean
    
        'allow the currently running animation to be specified by name
        Private _CurrentAnimationName As String
        Public Property CurrentAnimationName As String
            Get
                Return _CurrentAnimationName
            End Get
            Set(value As String)
                SetCurrentAnimation(value)
            End Set
        End Property
    
        'associate a rotation, scaling, skew, and translation transform with the sprite
        Public ReadOnly Property Rotation As New Windows.Media.RotateTransform
        Public ReadOnly Property Scaling As New Windows.Media.ScaleTransform
        Public ReadOnly Property Skew As New Windows.Media.SkewTransform
        Public ReadOnly Property Translation As New Windows.Media.TranslateTransform
    
        Public Sub New()
            'create a new transform group for this instance and add the associated transforms to it
            Dim tg = New Windows.Media.TransformGroup
            Transform = tg
            tg.Children.Add(Rotation)
            tg.Children.Add(Scaling)
            tg.Children.Add(Skew)
            tg.Children.Add(Translation)
    
            'provide the drawing instructions
            Dim context As Windows.Media.DrawingContext = RenderOpen()
            context.DrawDrawing(image)
            context.Close()
        End Sub
    
        'API methods for handing mouse input to a particular sprite (if enabled in the GameInput class)
        Protected Friend Overridable Sub OnMouseDown()
    
        End Sub
    
        Protected Friend Overridable Sub OnMouseMove()
    
        End Sub
    
        Protected Friend Overridable Sub OnMouseUp()
            'example of handling mouse clicks
            If Opacity = 1.0 Then
                Opacity = 0.4
            Else
                Opacity = 1.0
            End If
        End Sub
    
        Public Sub LoadAnimation(source As SpriteSheet, animationName As String, animationIndex As Integer, frameSize As Windows.Size, framesHorizontal As Boolean, flip As SpriteFlipDirection)
            _CurrentAnimationName = animationName
            animationFrames(animationName) = source.GetAnimationFrames(frameSize, framesHorizontal, animationIndex, flip)
        End Sub
    
        Protected Friend Overridable Sub SetCurrentAnimation(animationName As String)
            If _CurrentAnimationName = animationName Then Exit Sub
            If Not animationFrames.ContainsKey(animationName) Then Throw New ArgumentException("No such animation.")
            _CurrentAnimationName = animationName
    
            'setting the ImageSource and destination Rect are the primary operations for selecting the image to render
            image.ImageSource = animationFrames(animationName).ElementAt(0)
            image.Rect = New Windows.Rect(0, 0, image.ImageSource.Width, image.ImageSource.Height)
    
            'animation timing is reset
            frameTime = 1 / animationFrames(animationName).Count
            frameIndex = 0
    
            'transforms are recentered
            Scaling.CenterX = image.Rect.Width / 2
            Scaling.CenterY = image.Rect.Height / 2
            Skew.CenterX = Scaling.CenterX
            Skew.CenterY = Scaling.CenterY
            Rotation.CenterX = Scaling.CenterX
            Rotation.CenterY = Scaling.CenterY
        End Sub
    
        Protected Friend Overridable Sub UpdateCurrentAnimation(renderTime As Double)
            If Not String.IsNullOrEmpty(_CurrentAnimationName) Then
                If AnimationActive Then
                    frameTime -= renderTime
                    If frameTime <= 0 Then
                        Dim cnt = animationFrames(_CurrentAnimationName).Count
                        frameTime = 1 / cnt
                        frameIndex += 1
                        If frameIndex = cnt Then frameIndex = 0
                        image.ImageSource = animationFrames(_CurrentAnimationName)(frameIndex)
                    End If
                    isStillFrame = False
                ElseIf Not isStillFrame Then
                    image.ImageSource = animationFrames(_CurrentAnimationName)(0)
                    isStillFrame = True
                End If
            End If
        End Sub
    End Class
    

    This makes up the majority of the code needed for rendering WPF objects in a windows forms application.  The last class we will add is a GameInput class which handles the Keyboard and Mouse input for the API.  The keyboard is handled through the static Windows.Input.Keyboard class and the mouse is handled through events on the SpriteCanvas instance.

    Option Strict On
    
    Public Class GameInput
        Public Shared ReadOnly Property LastMousePosition As Windows.Point
    
        Public Shared Property MouseDownHitTesting As Boolean
        Public Shared Property MouseMoveHitTesting As Boolean
        Public Shared Property MouseUpHitTesting As Boolean = True
    
        Private Shared canvas As SpriteCanvas
        Private Shared currentKeyState As New Dictionary(Of Windows.Input.Key, Boolean)
        Private Shared lastKeyState As New Dictionary(Of Windows.Input.Key, Boolean)
    
        Public Shared Sub Initialize(spriteCanvas As SpriteCanvas)
            canvas = spriteCanvas
            AddHandler spriteCanvas.MouseDown, AddressOf SpriteCanvas_MouseDown
            AddHandler spriteCanvas.MouseMove, AddressOf SpriteCanvas_MouseMove
            AddHandler spriteCanvas.MouseUp, AddressOf SpriteCanvas_MouseUp
            For Each key As Windows.Input.Key In [Enum].GetValues(GetType(Windows.Input.Key))
                If key = Windows.Input.Key.None Then Continue For
                currentKeyState(key) = False
                lastKeyState(key) = False
            Next
        End Sub
    
        '[KEYBOARD INPUT HANDLING]
        Public Shared Function IsKeyDown(key As Windows.Input.Key) As Boolean
            If currentKeyState.ContainsKey(key) Then Return currentKeyState(key) = True
            Return False
        End Function
    
        Public Shared Function IsKeyUp(key As Windows.Input.Key) As Boolean
            If currentKeyState.ContainsKey(key) Then Return currentKeyState(key) = False
            Return True
        End Function
    
        Protected Friend Shared Sub Update()
            For Each key As Windows.Input.Key In [Enum].GetValues(GetType(Windows.Input.Key))
                If key = Windows.Input.Key.None Then Continue For
                lastKeyState(key) = currentKeyState(key)
                currentKeyState(key) = Windows.Input.Keyboard.IsKeyDown(key)
            Next
        End Sub
    
        Public Shared Function WasKeyPressed(key As Windows.Input.Key) As Boolean
            If lastKeyState.ContainsKey(key) Then Return lastKeyState(key) AndAlso Not currentKeyState(key)
            Return False
        End Function
        '[/KEYBOARD INPUT HANDLING]
    
        '[MOUSE INPUT HANDLING]
        Private Shared Function MouseDown_Callback(ByVal result As Windows.Media.HitTestResult) As Windows.Media.HitTestResultBehavior
            If GetType(Sprite).IsInstanceOfType(result.VisualHit) Then
                CType(result.VisualHit, Sprite).OnMouseDown()
            End If
            Return Windows.Media.HitTestResultBehavior.Continue
        End Function
    
        Private Shared Function MouseMove_Callback(ByVal result As Windows.Media.HitTestResult) As Windows.Media.HitTestResultBehavior
            If GetType(Sprite).IsInstanceOfType(result.VisualHit) Then
                CType(result.VisualHit, Sprite).OnMouseMove()
            End If
            Return Windows.Media.HitTestResultBehavior.Continue
        End Function
    
        Private Shared Function MouseUp_Callback(ByVal result As Windows.Media.HitTestResult) As Windows.Media.HitTestResultBehavior
            If GetType(Sprite).IsInstanceOfType(result.VisualHit) Then
                CType(result.VisualHit, Sprite).OnMouseUp()
            End If
            Return Windows.Media.HitTestResultBehavior.Continue
        End Function
    
        Private Shared Sub SpriteCanvas_MouseDown(sender As Object, e As Windows.Input.MouseButtonEventArgs)
            _LastMousePosition = e.GetPosition(CType(sender, Windows.UIElement))
            If MouseDownHitTesting Then DoHitTest(_LastMousePosition, AddressOf MouseDown_Callback)
        End Sub
    
        Private Shared Sub SpriteCanvas_MouseMove(sender As Object, e As Windows.Input.MouseEventArgs)
            _LastMousePosition = e.GetPosition(CType(sender, Windows.UIElement))
            If MouseMoveHitTesting Then DoHitTest(_LastMousePosition, AddressOf MouseMove_Callback)
        End Sub
    
        Private Shared Sub SpriteCanvas_MouseUp(sender As Object, e As Windows.Input.MouseButtonEventArgs)
            _LastMousePosition = e.GetPosition(CType(sender, Windows.UIElement))
            If MouseUpHitTesting Then DoHitTest(_LastMousePosition, AddressOf MouseUp_Callback)
        End Sub
        '[/MOUSE INPUT HANDLING]
    
        Public Shared Sub DoHitTest(point As Windows.Point, resultCallback As Windows.Media.HitTestResultCallback)
            Windows.Media.VisualTreeHelper.HitTest(canvas, Nothing,
                                                   resultCallback,
                                                   New Windows.Media.PointHitTestParameters(point))
        End Sub
    
    End Class
    

    Finally comes the Form1 code for the example project.  This code sets up the hosted WPF SpriteCanvas control in a Integration.HostElement control and initializes a test sprite object.  An example "game loop" is then implemented for the example sprite object.  Most of this code would actually reside in the strongly-typed sprite instance(s) created by the game, but appears in the WPF_Render event handler for simplicity of the example.

    Public Class Form1
        'required controls to host WPF elements in the Form
        Private host As New Integration.ElementHost()
        Private WithEvents canvas As New SpriteCanvas
    
        'test objects for the example
        Private spriteSheet1 As SpriteSheet
        Private sprite1 As New Sprite
    
        Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
            'setup the WinForms host control to display the WPF canvas in the Form
            host.Child = canvas
            host.Dock = DockStyle.Fill
            Controls.Add(host)
    
            'initialize game input with canvas
            GameInput.Initialize(canvas)
            GameInput.MouseUpHitTesting = True
    
            'create an example spritesheet and configure the example sprite instance
            spriteSheet1 = New SpriteSheet(IO.Path.Combine(My.Application.Info.DirectoryPath, "testgirl1.png"))
            sprite1.LoadAnimation(spriteSheet1, "Walk Down", 0, New Windows.Size(96, 132), True, SpriteFlipDirection.None)
            sprite1.LoadAnimation(spriteSheet1, "Walk Up", 1, New Windows.Size(96, 132), True, SpriteFlipDirection.None)
            sprite1.LoadAnimation(spriteSheet1, "Walk Right", 2, New Windows.Size(96, 132), True, SpriteFlipDirection.None)
            sprite1.LoadAnimation(spriteSheet1, "Walk Left", 2, New Windows.Size(96, 132), True, SpriteFlipDirection.Horizontal)
            'set the initial animation
            sprite1.CurrentAnimationName = "Walk Down"
            'add the test sprite to the canvas
            canvas.Children.Add(sprite1)
    
            'setup main game loop by handling the per-frame rendering event provided by WPF;
            'this also allows us to bypass the default animation engine in WPF, which we won't use
            '  because we want to give our animations the ability to respond to game state.
            'do this last to avoid errors with attempting to run uninitialized game resources.
            AddHandler Windows.Media.CompositionTarget.Rendering, AddressOf Wpf_Render
        End Sub
    
        'TODO:  Build GameClock object for time keeping
        Private lastRender As TimeSpan
    
        'local variables for tests in render routine
        Private scaleOffset As Double = -0.01
        Private moveDelta As Double = 1
        Private scalingOn, rotationOn, translationOn As Boolean
    
        Private Sub Wpf_Render(sender As Object, e As Windows.Media.RenderingEventArgs)
            'update game input every frame
            GameInput.Update()
    
            'test keyboard input handling
            'TODO: refactor into sprite class and/or derivatives
            '----------------------------------------------------------------------------------------------
            If GameInput.IsKeyDown(Windows.Input.Key.W) Then
                sprite1.CurrentAnimationName = "Walk Up"
                sprite1.AnimationActive = True
            ElseIf GameInput.IsKeyDown(Windows.Input.Key.S) Then
                sprite1.CurrentAnimationName = "Walk Down"
                sprite1.AnimationActive = True
            ElseIf GameInput.IsKeyDown(Windows.Input.Key.D) Then
                sprite1.CurrentAnimationName = "Walk Right"
                sprite1.AnimationActive = True
            ElseIf GameInput.IsKeyDown(Windows.Input.Key.A) Then
                sprite1.CurrentAnimationName = "Walk Left"
                sprite1.AnimationActive = True
            Else
                sprite1.AnimationActive = False
            End If
    
            If GameInput.WasKeyPressed(Windows.Input.Key.D1) Then scalingOn = Not scalingOn
            If GameInput.WasKeyPressed(Windows.Input.Key.D2) Then rotationOn = Not rotationOn
            If GameInput.WasKeyPressed(Windows.Input.Key.D3) Then translationOn = Not translationOn
    
            If scalingOn Then
                'test scaling sprite
                If sprite1.Scaling IsNot Nothing Then
                    If sprite1.Scaling.ScaleX < 0.01 OrElse sprite1.Scaling.ScaleX > 1.0 Then
                        scaleOffset *= -1
                    End If
                    sprite1.Scaling.ScaleX += scaleOffset
                    sprite1.Scaling.ScaleY = sprite1.Scaling.ScaleX
                End If
            End If
    
            If rotationOn Then
                'test rotating sprite
                sprite1.Rotation.Angle += 1
                If sprite1.Rotation.Angle > 360 Then sprite1.Rotation.Angle -= 360
            End If
    
            If translationOn Then
                'test moving sprite
                sprite1.Translation.X += moveDelta
                If sprite1.Translation.X > canvas.ActualWidth - sprite1.DescendantBounds.Width OrElse sprite1.Translation.X < 0 Then
                    moveDelta *= -1
                End If
            End If
    
            'test animating sprite
            sprite1.UpdateCurrentAnimation(e.RenderingTime.Subtract(lastRender).TotalSeconds)
            '-------------------------------------------------------------------------------------------------
    
            'update render time; TODO: replace with clock update
            lastRender = e.RenderingTime
        End Sub
    End Class

    One of the big advantages here is that we do not need to process our own "game loop".  The Wpf_Render event will be raised on each frame by the WPF engine so we can simply execute our per-frame game logic in this event handler - providing we don't take too long to do so.  Again, I haven't stress tested this enough to know the limitations on the amount of logic you can execute in this event handler without seeing a performance degradation, but I expect to be able to perform a significant amount of processing based on my experience with other DirectX-enabled rendering solutions.

    So that's pretty much it.  With this code you have the beginnings of an animation or game engine API for windows forms.  With some more supporting classes and a little refactoring, I suspect this could present a very solid solution for getting high-performance graphics into a Windows Forms solution with the smallest possible learning curve.


    Reed Kimble - "When you do things right, people won't be sure you've done anything at all"

    Tuesday, June 20, 2017 9:26 PM
    Moderator

All replies

  • Very Nice Reed!

    Excuse my ignorance on the subject, I have never done wpf from vb before.

    Am I supposed to add anything else or tie in Wpf somehow? Or should I be able to make the project and run it with what you have posted here?

    I made a vb win forms project, added this code in the form and made the classes. Option infer on, .net 4.6.1.

    VS tells me that


    PS added the references.
     VS 2015.
    Tuesday, June 20, 2017 11:43 PM
  • One of the big advantages here is that we do not need to process our own "game loop".  The Wpf_Render event will be raised on each frame by the WPF engine so we can simply execute our per-frame game logic in this event handler - providing we don't take too long to do so


    Reed Kimble - "When you do things right, people won't be sure you've done anything at all"

    Cheers Reed

    Thanks for sharing this. I have not got time now to try this - - but it may be a way to achieve smooth animations.


    Pride is the most destructive force in the universe

    Wednesday, June 21, 2017 12:24 AM
  • Very Nice Reed!

    Excuse my ignorance on the subject, I have never done wpf from vb before.

    Am I supposed to add anything else or tie in Wpf somehow? Or should I be able to make the project and run it with what you have posted here?

    I made a vb win forms project, added this code in the form and made the classes. Option infer on, .net 4.6.1.

    VS tells me that


    PS added the references.
     VS 2015.

    It should have worked as posted... I'm not sure about that error... are you sure you got the code for SpriteCanvas completely?  SpriteCanvas inherits FrameworkElement which inherits UIElement so I don't know why you would get that error if the code is correct.

    I'm also using VS2015 so that's not the issue...


    Reed Kimble - "When you do things right, people won't be sure you've done anything at all"

    Wednesday, June 21, 2017 12:41 AM
    Moderator
  • It should have worked as posted... I'm not sure about that error... are you sure you got the code for SpriteCanvas completely?  SpriteCanvas inherits FrameworkElement which inherits UIElement so I don't know why you would get that error if the code is correct.

    I'm also using VS2015 so that's not the issue...


    Reed Kimble - "When you do things right, people won't be sure you've done anything at all"

    Thanks, that was the problem extra line in spritecanvas.

    Now its running. I see the girls front on the form. Very cool. I have to study it now.

    I forgot does she animate in this example? I mean I cant get her to do anything if she is supposed to? 


    PS I see now. Use the ASWD keys to move/rotate.

    Wonderful!


    Wednesday, June 21, 2017 1:01 AM
  • Also, press 1, 2, or 3 to toggle the scaling, rotation, and translation effects. :)

    Reed Kimble - "When you do things right, people won't be sure you've done anything at all"

    Wednesday, June 21, 2017 12:03 PM
    Moderator
  • I am attempting a simple vector drawing shell example.

    I adapted your example this way hoping to draw a line on the form but I don't see anything.

    It does however call the render event.

    So I don't know if you want to get into this in this thread? But does anyone know where I am going wrong or leaving out?

    PS It just hit me I must need to add the line to the canvas?

    PS4 As Thorsten showed I added the lines to the canvas and now it works.

    Public Class Form1
        Private host As New Integration.ElementHost()
        Private WithEvents canvas1 As New System.Windows.Controls.Canvas
    
        Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
            host.Child = canvas1
            host.Dock = DockStyle.Fill
            Controls.Add(host)
    
            For i As Integer = 1 To 7
                Dim t As New Windows.Shapes.Line
                With t
                    .X1 = 100
                    .Y1 = 20
                    .X2 = 200 - (20 * i)
                    .Y2 = 100 + (20 * i)
                    .Stroke = Windows.Media.Brushes.Red
                    .StrokeThickness = "4"
    
                    canvas1.Children.Add(t)
    
                End With
            Next
    
        End Sub
    End Class
    

    Thursday, June 22, 2017 12:08 AM
  • I am attempting a simple vector drawing shell example.

    I adapted your example this way hoping to draw a line on the form but I don't see anything.

    It does however call the render event.

    So I don't know if you want to get into this in this thread? But does anyone know where I am going wrong or leaving out?

    PS It just hit me I must need to add the line to the canvas?

    Public Class Form1
        Private host As New Integration.ElementHost()
        Private WithEvents canvas1 As New System.Windows.Controls.Canvas
    
        Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
            host.Child = canvas1
            host.Dock = DockStyle.Fill
            Controls.Add(host)
    
            AddHandler Windows.Media.CompositionTarget.Rendering, AddressOf Wpf_Render
    
        End Sub
    
        Private Sub Wpf_Render(sender As Object, e As Windows.Media.RenderingEventArgs)
            Dim t As New Windows.Shapes.Line
            With t
                .X1 = 100
                .Y1 = 100
                .X2 = 200
                .Y2 = 200
    
                .Stroke = Windows.Media.Brushes.Black
    
                .StrokeThickness = "4"
    
            End With
        End Sub
    End Class


    Hi Tommy,

    you do not add the Line to the canvas children collection...

      canvas1.Children.Add(t)

    Regards,

      Thorsten

    Thursday, June 22, 2017 12:23 AM

  • Hi Tommy,

    you do not add the Line to the canvas children collection...

      canvas1.Children.Add(t)

    Regards,

      Thorsten

    Ok, thanks. I updated my example.

    Thursday, June 22, 2017 1:11 AM

  • PS3.. oh wait... I think maybe it should not be added in the render event?



    Yes, dont add it in the event that handles the updating of the CompositionTarget, it would be added a line on each new frame..

    Typically you wont create a shape in a "drawing" method, else you create it anew on every new frame thats drawn. In WinForms you wouldnt add content in a paint method, you just would draw the existing objects...

    Here's a small WPF example that uses the same concept (file: FCanvas). The balls and eggs are added in a button's click event.

    https://1drv.ms/u/s!Aunk9dshveXVok2A0GGDz2IGOSmx

    Regards,

      Thorsten


    Thursday, June 22, 2017 1:30 AM

  • PS3.. oh wait... I think maybe it should not be added in the render event?



    Yes, dont add it in the event that handles the updating of the CompositionTarget, it would be added a line on each new frame..

    Regards,

      Thorsten

    I updated the example again just adding some lines in form load.


    Thursday, June 22, 2017 1:41 AM
  • Here is a time comparison I made between the normal GDI ? drawing method and the WPF method I showed above to draw lines.

    I think the tests themselves are similar? Not sure what is being tested exactly.

    The GDI seems a bit faster as far as this test goes.

    Of course all this does is draw lines and there are things in wpf that are not available in vb and etc.

    I realize this tests making the objects and adding to lists.

    I am not sure how to time just the pure drawing side of the wpf? 

    Form 1 is the GDI test and shows form2.

    Public Class Form1
        Private sw As New Stopwatch
        Private teststage, testcount, testtime As Integer
        Private Lines As New List(Of LineShape)
        Structure LineShape
            Public x1 As Single
            Public y1 As Single
            Public x2 As Single
            Public y2 As Single
            Public linewidth As Single
            Public color As Color
        End Structure
        Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
            DoubleBuffered = True
    
            Button1.Text = "GDI Test"
            ClientSize = New Size(300, 400)
            Timer1.Interval = 100
    
            Form2.Show()
    
        End Sub
    
        Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick
            testcount += 1
    
            If testcount >= 10 Then
                Timer1.Stop()
                Text = "dt = " & (testtime / 10).ToString
            Else
                Text = testcount
    
                teststage = 1
                sw.Reset()
                sw.Start()
                Lines.Clear()
    
                For i As Integer = 0 To 200
                    Dim t As New LineShape
                    With t
                        .X1 = 1
                        .Y1 = 2 * i
                        .X2 = 300
                        .Y2 = 150
                        .color = Color.Red
                        .linewidth = 1
    
                        Lines.Add(t)
    
                    End With
                Next
            End If
    
            Refresh()
    
        End Sub
    
        Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
            testtime = 0
            testcount = 0
            Timer1.Start()
    
        End Sub
    
        Private Sub Form1_Paint(sender As Object, e As PaintEventArgs) Handles Me.Paint
            e.Graphics.SmoothingMode = Drawing2D.SmoothingMode.AntiAlias
    
            For Each l As LineShape In Lines
                Using p As New Pen(New SolidBrush(l.color), l.linewidth)
                    e.Graphics.DrawLine(p, l.x1, l.y1, l.x2, l.y2)
                End Using
            Next
    
            If teststage = 1 Then
                sw.Stop()
                teststage = 2
                testtime += sw.ElapsedMilliseconds
            End If
        End Sub
    End Class
    
    

    Form2:

    Public Class Form2
        Private host As New Integration.ElementHost()
        Private WithEvents Canvas1 As New System.Windows.Controls.Canvas
        Private sw As New Stopwatch
        Private teststage, testcount, testtime As Integer
    
        Private Sub Form2_Load(sender As Object, e As EventArgs) Handles MyBase.Load
            ClientSize = New Size(300, 400)
            Timer1.Interval = 100
    
    
            host.Child = Canvas1
            host.Dock = DockStyle.Fill
            Controls.Add(host)
            Button1.Text = "Wpf Test"
    
            AddHandler Windows.Media.CompositionTarget.Rendering, AddressOf Wpf_Render
    
        End Sub
        Private Sub Wpf_Render(sender As Object, e As Windows.Media.RenderingEventArgs)
            If teststage = 1 Then
                sw.Stop()
                teststage = 2
                testtime += sw.ElapsedMilliseconds
            End If
        End Sub
    
        Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
            testtime = 0
            testcount = 0
            Timer1.Start()
    
        End Sub
    
        Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick
            testcount += 1
    
            If testcount >= 10 Then
                Timer1.Stop()
                Text = "dt = " & (testtime / 10).ToString
            Else
                Text = testcount
    
                teststage = 1
                sw.Reset()
                sw.Start()
                Canvas1.Children.Clear()
    
                For i As Integer = 0 To 200
                    Dim t As New Windows.Shapes.Line
                    With t
                        .X1 = 1
                        .Y1 = 2 * i
                        .X2 = 300
                        .Y2 = 150
                        .Stroke = Windows.Media.Brushes.Red
                        .StrokeThickness = "1"
    
                        Canvas1.Children.Add(t)
    
                    End With
                Next
            End If
        End Sub
    End Class
    Thursday, June 22, 2017 9:27 PM
  • Here is a time comparison I made between the normal GDI ? drawing method and the WPF method I showed above to draw lines.

    I think the tests themselves are similar? Not sure what is being tested exactly.

    The GDI seems a bit faster as far as this test goes.

    Of course all this does is draw lines and there are things in wpf that are not available in vb and etc.

    I realize this tests making the objects and adding to lists.

    I am not sure how to time just the pure drawing side of the wpf? 


    Hi,

    maybe

    https://social.msdn.microsoft.com/Forums/vstudio/en-US/95300fe6-8958-4b9b-ad37-581a9bdefccf/compositiontargetrendering-and-measuring-frame-rate?forum=wpf

    https://docs.microsoft.com/en-us/dotnet/framework/wpf/graphics-multimedia/how-to-render-on-a-per-frame-interval-using-compositiontarget

    Also check the post intorducing the "DesiredFramerate" here:

    https://stackoverflow.com/questions/4587197/any-way-to-force-compositiontarget-rendering-run-x-frames-per-second

    but also this:

    https://stackoverflow.com/questions/7733990/constant-framerate-in-wpf-for-game

    and (especially) this:

    https://stackoverflow.com/questions/5812384/why-is-frame-rate-in-wpf-irregular-and-not-limited-to-monitor-refresh

    Regards,

      Thorsten



    Thursday, June 22, 2017 9:41 PM
  • Thorsten,

    Like I said I am not sure how to time it.

    :)

    I looked at the links you provided, thanks.

    I think one is going to need some animation that one can see with the eye which is faster?

    Then each something event, maybe even a win forms timer set at 10ms, the data is updated ie postion of a tank on a game screen or even move the entire screen of tiles to follow tank movement. One of the threads you gave Thorsten sort of mentions that. Also seems Reed did in his writeup above. A win forms timer will only run as fast as the drawing can keep up. At least that is what happens in the GDI (when the screen is refreshed in the timer tick).

    I suspect WPF will be much faster. Just based on how much faster directx 9c is compared to attempting to draw 3d with gdi only in vb. Of course that is 3d.

    And of course it is hard for me to whip out a drawing in wpf since I dont know it.


    Thursday, June 22, 2017 10:18 PM

  • I suspect WPF will be much faster. Just based on how much faster directx 9c is compared to attempting to draw 3d with gdi only in vb. Of course that is 3d.

    And of course it is hard for me to whip out a drawing in wpf since I dont know it.


    Hi Tommy,

    Wpf is not really faster, but it [tries to] use(s) the gpu more than gdi+ does - depending on the settings. So Animations may run smoother since there's some synchronization with the monitor refresh rate. For very complex Drawings/Animations you can "set" or "hint" the framerate in Wpf.

    Wpf drawing in general is a bit different to gdi+, but you can use a DrawingVisual with its DrawingContext *almost* as a direct paint option in gdi+.

    Regards,

      Thorsten

    Friday, June 23, 2017 9:21 PM
  • I see lots of good built in things in Reed's example like dont have to write as much code for collisions? Other organizing tools etc.

    However, why not just use wpf?

    Not that I can so thats my excuse for using vb I guess. Its hard enough just to relearn how to draw a line in wpf. Saving files, making internet calls, or whatever took mucho time to learn in vb and I already have it working.

    And, finally, forget rotating buttons, what I personally need is the latest version of Directx 3d. Now there I can see things in the wpf examples I already know from using directx9c.


    PS I mean hit testing things for collisions or just clicking an object.
    Friday, June 23, 2017 11:43 PM

  • However, why not just use wpf?

    [...]

    PS I mean hit testing things for collisions or just clicking an object.

    Hi,

    well, use Wpf, if you like. There's much things that are easier as in WinForms, but there also is a lot of concepts to learn. Getting used to Xaml is maybe the hardest one, since - in the beginning - it might look as a step back. So, when you want to learn it, take a book or two and start a project you will work on for a longer time. Because after some weeks with Xaml and the Wpf concepts, you'll begin to like it. But today I think, learning Wpf is not a "must do" for hobbyist-programmers anymore. Microsoft advertises a new and "so good" technology every couple of years now...

    I prefer WindowsForms, because I dont have ambitions to write fancy gadgets, but programs to work with on a Windows Desktop. (I still use my Computer the way I did 15 years ago) They may not look very modern, but my intention is that the "art" is what you produce with those programs. And WindowsForms - for me - still is the technology in which I can do the Designe steps in the fastest way. Wpf I use when there's no good way to achieve the aim in WindowsForms, like my 3d mapping program for Images (onto spheres, eggs, drop-shaped objects etc.)

    Other people may think different, so maybe just try it.

    Regards,

      Thorsten

    Saturday, June 24, 2017 12:59 AM
  • Here is a time comparison I made between the normal GDI ? drawing method and the WPF method I showed above to draw lines.

    I think the tests themselves are similar? Not sure what is being tested exactly.

    The GDI seems a bit faster as far as this test goes.

    Of course all this does is draw lines and there are things in wpf that are not available in vb and etc.

    I realize this tests making the objects and adding to lists.

    I am not sure how to time just the pure drawing side of the wpf? 

    Form 1 is the GDI test and shows form2.

    Public Class Form1
        Private sw As New Stopwatch
        Private teststage, testcount, testtime As Integer
        Private Lines As New List(Of LineShape)
        Structure LineShape
            Public x1 As Single
            Public y1 As Single
            Public x2 As Single
            Public y2 As Single
            Public linewidth As Single
            Public color As Color
        End Structure
        Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
            DoubleBuffered = True
    
            Button1.Text = "GDI Test"
            ClientSize = New Size(300, 400)
            Timer1.Interval = 100
    
            Form2.Show()
    
        End Sub
    
        Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick
            testcount += 1
    
            If testcount >= 10 Then
                Timer1.Stop()
                Text = "dt = " & (testtime / 10).ToString
            Else
                Text = testcount
    
                teststage = 1
                sw.Reset()
                sw.Start()
                Lines.Clear()
    
                For i As Integer = 0 To 200
                    Dim t As New LineShape
                    With t
                        .X1 = 1
                        .Y1 = 2 * i
                        .X2 = 300
                        .Y2 = 150
                        .color = Color.Red
                        .linewidth = 1
    
                        Lines.Add(t)
    
                    End With
                Next
            End If
    
            Refresh()
    
        End Sub
    
        Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
            testtime = 0
            testcount = 0
            Timer1.Start()
    
        End Sub
    
        Private Sub Form1_Paint(sender As Object, e As PaintEventArgs) Handles Me.Paint
            e.Graphics.SmoothingMode = Drawing2D.SmoothingMode.AntiAlias
    
            For Each l As LineShape In Lines
                Using p As New Pen(New SolidBrush(l.color), l.linewidth)
                    e.Graphics.DrawLine(p, l.x1, l.y1, l.x2, l.y2)
                End Using
            Next
    
            If teststage = 1 Then
                sw.Stop()
                teststage = 2
                testtime += sw.ElapsedMilliseconds
            End If
        End Sub
    End Class
    

    Form2:

    Public Class Form2
        Private host As New Integration.ElementHost()
        Private WithEvents Canvas1 As New System.Windows.Controls.Canvas
        Private sw As New Stopwatch
        Private teststage, testcount, testtime As Integer
    
        Private Sub Form2_Load(sender As Object, e As EventArgs) Handles MyBase.Load
            ClientSize = New Size(300, 400)
            Timer1.Interval = 100
    
    
            host.Child = Canvas1
            host.Dock = DockStyle.Fill
            Controls.Add(host)
            Button1.Text = "Wpf Test"
    
            AddHandler Windows.Media.CompositionTarget.Rendering, AddressOf Wpf_Render
    
        End Sub
        Private Sub Wpf_Render(sender As Object, e As Windows.Media.RenderingEventArgs)
            If teststage = 1 Then
                sw.Stop()
                teststage = 2
                testtime += sw.ElapsedMilliseconds
            End If
        End Sub
    
        Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
            testtime = 0
            testcount = 0
            Timer1.Start()
    
        End Sub
    
        Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick
            testcount += 1
    
            If testcount >= 10 Then
                Timer1.Stop()
                Text = "dt = " & (testtime / 10).ToString
            Else
                Text = testcount
    
                teststage = 1
                sw.Reset()
                sw.Start()
                Canvas1.Children.Clear()
    
                For i As Integer = 0 To 200
                    Dim t As New Windows.Shapes.Line
                    With t
                        .X1 = 1
                        .Y1 = 2 * i
                        .X2 = 300
                        .Y2 = 150
                        .Stroke = Windows.Media.Brushes.Red
                        .StrokeThickness = "1"
    
                        Canvas1.Children.Add(t)
    
                    End With
                Next
            End If
        End Sub
    End Class

    Tommy,

    In WPF you would not recreate those line shapes in the timer.  The lines would be created once and held in some reference variable.  Then you can add or remove them from the canvas as needed.  But there's no need to rebuild them.

    One of the biggest differences between GDI drawing and WPF rendering is immediate mode versus retained mode rendering.  GDI is immediate mode rendering - the moment a drawing command executes, the output goes to the video buffer and is displayed on screen.  WPF is retained mode rendering - when a drawing command executes, the output instruction is stored with the drawing object.  Later, whenever the drawing object is actually rendered, the stored instructions are executed.

    So the drawing commands in WPF need only be executed once.  After that you generally make changes with transforms.

    So as far as render timing goes, the time required to build the drawing instruction list for the WPF object should not be counted.

    This section of the documentation contains some pretty good write-ups on how WPF rendering works:

    https://docs.microsoft.com/en-us/dotnet/framework/wpf/graphics-multimedia/


    Reed Kimble - "When you do things right, people won't be sure you've done anything at all"

    Saturday, June 24, 2017 1:36 PM
    Moderator

  • I suspect WPF will be much faster. Just based on how much faster directx 9c is compared to attempting to draw 3d with gdi only in vb. Of course that is 3d.

    And of course it is hard for me to whip out a drawing in wpf since I dont know it.


    Hi Tommy,

    Wpf is not really faster, but it [tries to] use(s) the gpu more than gdi+ does - depending on the settings. So Animations may run smoother since there's some synchronization with the monitor refresh rate. For very complex Drawings/Animations you can "set" or "hint" the framerate in Wpf.

    Wpf drawing in general is a bit different to gdi+, but you can use a DrawingVisual with its DrawingContext *almost* as a direct paint option in gdi+.

    Regards,

      Thorsten

    Thorsten,

    To my knowledge, GDI/GDI+ has no access to the GPU... the tech predates the existence of dedicated graphics subprocessors.  As I understand it, WPF is fully GPU-aware and will use as much GPU as possible, avoiding the CPU for most rendering operations.  I'm definitely seen far less CPU usage in the performance monitor with the WPF version of this example animation versus doing the same animation in GDI (never mind the transforms, just the animation part).


    Reed Kimble - "When you do things right, people won't be sure you've done anything at all"

    Saturday, June 24, 2017 1:42 PM
    Moderator
  • I see lots of good built in things in Reed's example like dont have to write as much code for collisions? Other organizing tools etc.

    However, why not just use wpf?

    Not that I can so thats my excuse for using vb I guess. Its hard enough just to relearn how to draw a line in wpf. Saving files, making internet calls, or whatever took mucho time to learn in vb and I already have it working.

    And, finally, forget rotating buttons, what I personally need is the latest version of Directx 3d. Now there I can see things in the wpf examples I already know from using directx9c.


    PS I mean hit testing things for collisions or just clicking an object.

    Tommy,

    As Thorsten also said, learning the XAML for the front end is challenge #1.  This "borrowing" of WPF into WinForms means you can continue to work with the GUI designer and standard WinForms controls for most of your GUI interface and only need the WPF controls associated with the actual real-time rendering.

    One thing to consider about the XAML though is that it is not going away.  Even as WPF fades in favor of UWP (at least they hope!), XAML is still the front end used in UWP.  So there is an argument for learning XAML UI design regardless.

    As for your 3D needs, just look at the graphics link I previously posted and go to the 3D section of the documentation.  You should be able to create a Viewport3D to use a the go-between with the RenderCanvas from my example and any 3D WPF objects you want to render.


    Reed Kimble - "When you do things right, people won't be sure you've done anything at all"

    Saturday, June 24, 2017 1:48 PM
    Moderator
  • Reed,

    "In WPF you would not recreate those line shapes in the timer. "

    Yes. Thanks. Same for the GDI test.

    So as I mentioned I realized that if don't create the wpf objects each loop and force rendering event then there is nothing to time. And or I don't know how to time the wpf side.

    It was just my practice example to see what happens and then I realized wpf does not let you time the drawing exactly the same as gdi etc.  And now I am done testing.


    :)
    Saturday, June 24, 2017 5:24 PM
  • This "borrowing" of WPF into WinForms means you can continue to work with the GUI designer and standard WinForms controls for most of your GUI interface and only need the WPF controls associated with the actual real-time rendering.

    That's all I want. Its 10 years later than my 20 year old directX9c.

    The 3d directx drawing part, making objects, setting the view, lights, textures, all looks very similar to my directx9c code with a few < > \\ thrown in and other unknown.

    One declares a device window with the directx and then call directx functions with the device window handle and the objects and setup etc. and Whalaa!!!

    Saturday, June 24, 2017 9:55 PM