Windows Presentation Foundation (WPF) is a retained mode graphics system (see Retained Mode Versus Immediate Mode). That means when you write code to draw something, you’re actually declaring a set of graphics objects (like lines or shapes) to use. The graphics library then creates a model in memory. This model changes in response to your program to reflect changes in the scene. The scene is rendered to the display.

GDI, GDIPlus, and Direct2D are examples of immediate mode graphics systems. These directly draw objects on the display, and the program is directly responsible for updating these objects when needed.

For an example of Retained mode using WPF, see Cartoon animation program

For an example of Immediate mode using Windows Forms, see Cellular Automata: The Game of Life

What if you want to combine both an immediate mode and a retained mode graphic system? You can.

When I first started writing computer graphics programs, we had a 12 bit computer with 4K ram with an x and y output to an oscilloscope in 1972 (see My toys over the years). I could change the x or y axis inputs to make the scope draw. My program would have to repeatedly draw to see an image.

When I bought my first IBM PC in 1981, I had a choice of a monochrome character display card, which required its own monitor, or a color graphics card (which could output to my homebuilt HeathKit TV). Of course, I chose the color card, so I didn’t have to spend more money on a monitor. I started creating many simple graphics programs, including a Cartoon animation program

Using WPF retained mode graphics severely limited my ability to draw a pixel on the screen.

The code below shows how to light up pixels directly from a WPF program, using a WPF control called an HWndHost.

It draws ellipses directly on the display surface, then moves them. Motion is simulated by erasing the ball and drawing it again in a slightly different position.

Start Visual Studio

File->New->Project-> Visual Basic->Windows WPF Application

Replace the code in MainWindow.Xaml.vb with the code below

Hit F5 to watch the balls bounce around

Try hitting some of the command keys (see code) to vary the output.

You can use HWndHost to host a Win32 HWnd window inside a WPF element

<code>

'http://blogs.msdn.com/b/calvin_hsia/archive/2008/11/28/9155196.aspx
Option Strict On

Imports System.Runtime.InteropServices

Class MainWindow
  Dim _MovingBalls As MovingBalls
  Sub Load() Handles MyBase.Loaded
    Try
      Me.Width = 600
      Me.Height = 600

      AddHandler Me.SizeChanged,
          Sub()
                  _MovingBalls._doInit = True
                End Sub

      ' use this line if you want to have more
      ' complicated WPF
      'Me.Content = CreateUIElem("Button", "Bisque")

      _MovingBalls = New MovingBalls(Me)
      Me.Content = _MovingBalls

    Catch ex As Exception
      Me.Content = ex.ToString
    End Try
  End Sub
  Function CreateUIElem(
                       UIElemName As String,
                       BackGround As String) As UIElement
    Dim xaml = _
    <StackPanel
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      Name="HwndTest" Background=<%= BackGround %> Orientation="Vertical" Opacity=".5">
      <Button><%= UIElemName %></Button>
      <UserControl Name="MyBorder"></UserControl>
      <Button>Bottom Button</Button>
      <TextBlock Name="Another">some text</TextBlock>
    </StackPanel>
    Dim UIElem = CType(Markup.XamlReader.Load(xaml.CreateReader), StackPanel)

    Dim Border = CType(UIElem.FindName("MyBorder"), UserControl)
    _MovingBalls = New MovingBalls(Me)
    Border.Content = _MovingBalls
    Return UIElem
  End Function
  Protected Overrides Sub OnKeyDown(e As KeyEventArgs)
    _MovingBalls.OnKey(e)
  End Sub
End Class

Public Class MovingBalls
  Inherits MyHwndHost
  Dim _balls As Ball()
  Dim _ballSpeed As Integer = 10
  Dim _nBalls As Integer = 100
  Dim _rand As New Random(1)
  Dim _doErase As Boolean = True
  Dim IsClosing As Boolean = False
  Dim tmr As Timers.Timer
  Dim _wParent As Window
  Public _doInit As Boolean = True

  Public Sub New(wParent As Window)
    MyBase.New(CreateSolidBrush(CType(&HF88080, IntPtr))) ' Blue

    Dim IsClosing = False
    _wParent = wParent
    AddHandler wParent.Closing,
        Sub()
              IsClosing = True
            End Sub

    Dim tmr = New Timers.Timer With
              {
                  .Enabled = True,
                  .Interval = 30
              }
    'If Debugger.IsAttached Then
    '    tmr.Interval = 3000
    'End If
    AddHandler tmr.Elapsed,
        Sub()
              AnimateIt()
            End Sub
  End Sub
  Sub AnimateIt()
    Try
      If IsClosing Then
        tmr.Enabled = False
      End If
      Me.Dispatcher.
          Invoke(
          Sub()
                  If _doInit Then
                    _doInit = False
                    InitBalls()
                  End If
                  Dim hDC = GetDC(_hwnd) ' get device context
                  SelectObject(hDC, _hbrBackground)
                  For i = 0 To _nBalls - 1
                    Dim ball = _balls(i)
                    ' first we draw over the old ball with the back color to erase it
                    If _doErase Then
                      ball.Erase(hDC, _hbrBackground)
                    End If

                    ' now we detect wall collision
                    If ball._Position.x >= Me.ActualWidth OrElse
                            ball._Position.x < 0 Then
                      ball._Speed.x = -ball._Speed.x
                    End If
                    If ball._Position.y > Me.ActualHeight OrElse
                            ball._Position.y < 0 Then
                      ball._Speed.y = -ball._Speed.y
                    End If
                    ' now we move the ball
                    ball._Position += ball._Speed
                    ' now we draw the ball in the new position
                    ball.Draw(hDC)
                  Next
                  ReleaseDC(_hwnd, hDC)
                End Sub)
    Catch ex As Exception
      tmr.Enabled = False
      Me.Dispatcher.Invoke(
          Sub()
                  _wParent.Content = ex.ToString()
                End Sub)
    End Try
  End Sub

  Dim curColor As Integer = &HFFFFFF
  Public Sub InitBalls()
    EraseRect()
    If _balls IsNot Nothing Then
      For Each b In _balls
        If b IsNot Nothing Then
          b.Dispose()
        End If
      Next
    End If
    ReDim _balls(_nBalls)
    For i = 0 To _nBalls - 1
      curColor -= 100 ' // change the color some way
      _balls(i) = New Ball(curColor)
      _balls(i)._Speed = New POINT With {
          .x = _rand.Next(_ballSpeed) + 1,
          .y = _rand.Next(_ballSpeed) + 1
      }
    Next
  End Sub
  Sub OnKey(e As KeyEventArgs)
    Select Case e.Key
      Case Key.Subtract
        _ballSpeed -= 1
      Case Key.Add
        _ballSpeed += 1
      Case Key.E
        _doErase = Not _doErase
      Case Key.R
        _doInit = True
      Case Key.Q, Key.Escape
        End
    End Select
  End Sub

  Public Class MyObject
    Public _rect As RECT
    Public _ForeColor As IntPtr
    Public _Position As POINT
    Public _Speed As POINT
  End Class

  Public Class Ball
    Inherits MyObject
    Implements IDisposable

    Public Sub New(hbrBallColor As Integer)
      _rect = New RECT(0, 0, 10, 10)
      _Speed = New POINT(1, 1)
      _ForeColor = CreateSolidBrush(CType(hbrBallColor, IntPtr))
    End Sub
    Public Sub Draw(hdc As IntPtr)
      SelectObject(hdc, _ForeColor)
      Dim newPos = _rect.RectMove(_Position)
      Ellipse(hdc, newPos.Left, newPos.Top, newPos.Right, newPos.Bottom)
    End Sub
    Public Sub [Erase](hdc As IntPtr, hBackColor As IntPtr)
      FillRect(hdc, _rect.RectMove(_Position), hBackColor)
    End Sub

#Region "IDisposable Support"
    Private disposedValue As Boolean ' To detect redundant calls

    ' IDisposable
    Protected Overridable Sub Dispose(disposing As Boolean)
      If Not Me.disposedValue Then
        If disposing Then
          ' TODO: dispose managed state (managed objects).
        End If
        DeleteObject(_ForeColor)
        ' TODO: free unmanaged resources (unmanaged objects) and override Finalize() below.
        ' TODO: set large fields to null.
      End If
      Me.disposedValue = True
    End Sub

    Protected Overrides Sub Finalize()
      ' Do not change this code.  Put cleanup code in Dispose(ByVal disposing As Boolean) above.
      Dispose(False)
      MyBase.Finalize()
    End Sub

    ' This code added by Visual Basic to correctly implement the disposable pattern.
    Public Sub Dispose() Implements IDisposable.Dispose
      ' Do not change this code.  Put cleanup code in Dispose(disposing As Boolean) above.
      Dispose(True)
      GC.SuppressFinalize(Me)
    End Sub
#End Region

  End Class
End Class

Public Class MyHwndHost
  Inherits Interop.HwndHost
  Protected _hwnd As IntPtr
  Protected _hbrBackground As IntPtr
  Public Sub New(hbrBackground As IntPtr)
    _hbrBackground = hbrBackground
  End Sub
  Protected Overrides Function BuildWindowCore(hwndParent As HandleRef) As HandleRef
    _hwnd = CreateWindowEx(
        0,
        "static",
        "",
        WindowStyles.WS_CHILD Or
        WindowStyles.WS_CLIPCHILDREN Or
        WindowStyles.WS_CLIPSIBLINGS,
        0, 0, 200, 300,
        hwndParent.Handle,
        IntPtr.Zero,
        IntPtr.Zero,
        IntPtr.Zero)
    Return New HandleRef(Me, _hwnd)
  End Function
  Public Sub EraseRect()
    Dim r = New RECT
    GetClientRect(_hwnd, r)
    Dim hDC = GetDC(_hwnd)
    FillRect(hDC, r, _hbrBackground)
    ReleaseDC(_hwnd, hDC)
  End Sub

  Protected Overrides Function WndProc(
                                      hwnd As IntPtr,
                                      msg As Integer,
                                      wParam As IntPtr,
                                      lParam As IntPtr,
                                      ByRef handled As Boolean
                                      ) As IntPtr
    Select Case msg
      Case WM_.WM_ERASEBKGND
        EraseRect()
        handled = True
        Return New IntPtr(0)
      Case WM_.WM_PAINT
        handled = True
        Return IntPtr.Zero
    End Select
    Return MyBase.WndProc(hwnd, msg, wParam, lParam, handled)
  End Function

  Protected Overrides Sub DestroyWindowCore(hwnd As HandleRef)
    DeleteObject(_hbrBackground)
    DestroyWindow(hwnd.Handle)
  End Sub
End Class

Public Module NativeMethods
  Public Structure POINT
    Public Sub New(x As Integer, y As Integer)
      Me.x = x
      Me.y = y
    End Sub
    Public x As Integer
    Public y As Integer
    Public Shared Operator +(p1 As POINT, p2 As POINT) As POINT
      Dim pt As POINT
      pt.x = p1.x + p2.x
      pt.y = p1.y + p2.y
      Return pt
    End Operator
    Public Overrides Function ToString() As String
      Return String.Format("({0},{1})", x, y)
    End Function
  End Structure
  <StructLayout(LayoutKind.Sequential)>
  Public Structure RECT
    Public Sub New(
                  left As Integer,
                  top As Integer,
                  right As Integer,
                  bottom As Integer)
      Me.Left = left
      Me.Top = top
      Me.Right = right
      Me.Bottom = bottom
    End Sub
    Public Left As Integer
    Public Top As Integer
    Public Right As Integer
    Public Bottom As Integer
    Public Function RectMove(pt As POINT) As RECT
      Return New RECT() With {
          .Left = Left + pt.x,
          .Top = Top + pt.y,
          .Right = Right + pt.x,
          .Bottom = Bottom + pt.y}
    End Function
    Public Function ToRect() As System.Windows.Rect
      Return New System.Windows.Rect(Left, Top, Right - Left, Bottom - Top)
    End Function
    Public Overrides Function ToString() As String
      Return String.Format(
          "({0},{1}), ({2},{3})",
          Left,
          Top,
          Right,
          Bottom)
    End Function
  End Structure

#Region "WIN32 Defs"
  Public Enum COLOR
    COLOR_SCROLLBAR = 0
    COLOR_BACKGROUND = 1
    COLOR_DESKTOP = 1
    COLOR_ACTIVECAPTION = 2
    COLOR_INACTIVECAPTION = 3
    COLOR_MENU = 4
    COLOR_WINDOW = 5
    COLOR_WINDOWFRAME = 6
    COLOR_MENUTEXT = 7
    COLOR_WINDOWTEXT = 8
    COLOR_CAPTIONTEXT = 9
    COLOR_ACTIVEBORDER = 10
    COLOR_INACTIVEBORDER = 11
    COLOR_APPWORKSPACE = 12
    COLOR_HIGHLIGHT = 13
    COLOR_HIGHLIGHTTEXT = 14
    COLOR_BTNFACE = 15
    COLOR_3DFACE = 15
    COLOR_BTNSHADOW = 16
    COLOR_3DSHADOW = 16
    COLOR_GRAYTEXT = 17
    COLOR_BTNTEXT = 18
    COLOR_INACTIVECAPTIONTEXT = 19
    COLOR_BTNHIGHLIGHT = 20
    COLOR_3DHIGHLIGHT = 20
    COLOR_3DHILIGHT = 20
    COLOR_BTNHILIGHT = 20
    COLOR_3DDKSHADOW = 21
    COLOR_3DLIGHT = 22
    COLOR_INFOTEXT = 23
    COLOR_INFOBK = 24
  End Enum
  <DllImport("user32.dll", CharSet:=CharSet.Auto)> _
  Public Function GetClientRect(ByVal hWnd As System.IntPtr, _
       ByRef lpRECT As RECT) As Integer
  End Function

  Enum WM_
    WM_PAINT = &HF
    WM_ERASEBKGND = &H14
  End Enum
  <Flags()> _
  Enum WindowStyles
    WS_OVERLAPPED = &H0
    WS_POPUP = &H80000000
    WS_CHILD = &H40000000
    WS_MINIMIZE = &H20000000
    WS_VISIBLE = &H10000000
    WS_DISABLED = &H8000000
    WS_CLIPSIBLINGS = &H4000000
    WS_CLIPCHILDREN = &H2000000
    WS_MAXIMIZE = &H1000000
    WS_BORDER = &H800000
    WS_DLGFRAME = &H400000
    WS_VSCROLL = &H200000
    WS_HSCROLL = &H100000
    WS_SYSMENU = &H80000
    WS_THICKFRAME = &H40000
    WS_GROUP = &H20000
    WS_TABSTOP = &H10000
    WS_MINIMIZEBOX = &H20000
    WS_MAXIMIZEBOX = &H10000
    WS_CAPTION = WS_BORDER Or WS_DLGFRAME
    WS_TILED = WS_OVERLAPPED
    WS_ICONIC = WS_MINIMIZE
    WS_SIZEBOX = WS_THICKFRAME
    WS_TILEDWINDOW = WS_OVERLAPPEDWINDOW
    WS_OVERLAPPEDWINDOW = WS_OVERLAPPED Or WS_CAPTION Or WS_SYSMENU Or WS_THICKFRAME Or WS_MINIMIZEBOX Or WS_MAXIMIZEBOX
    WS_POPUPWINDOW = WS_POPUP Or WS_BORDER Or WS_SYSMENU
    WS_CHILDWINDOW = WS_CHILD
  End Enum
  <DllImport("user32.dll", SetLastError:=True, CharSet:=CharSet.Auto)> _
  Public Function ShowWindow(ByVal hwnd As IntPtr, ByVal nCmdShow As Int32) As Boolean
  End Function
  <DllImport("user32.dll")> _
  Public Function UpdateWindow( _
     ByVal hWnd As IntPtr) As <MarshalAs(UnmanagedType.Bool)> Boolean
  End Function
  <DllImport("user32.dll", CharSet:=CharSet.Auto)> _
  Public Function CreateWindowEx( _
         ByVal dwExStyle As UInteger, _
         ByVal lpClassName As String, _
         ByVal lpWindowName As String, _
         ByVal dwStyle As WindowStyles, _
         ByVal x As Integer, _
         ByVal y As Integer, _
         ByVal nWidth As Integer, _
         ByVal nHeight As Integer, _
         ByVal hWndParent As IntPtr, _
         ByVal hMenut As IntPtr, _
         ByVal hInstancet As IntPtr, _
         ByVal lpParamt As IntPtr) As IntPtr
  End Function

  <DllImport("user32.dll", SetLastError:=True, CharSet:=CharSet.Auto)> _
  Public Function DestroyWindow(ByVal hwnd As IntPtr) As Boolean
  End Function

  Const CW_USEDEFAULT As Int32 = &H80000000

  Public Enum Show_Window
    SW_HIDE = 0
    SW_SHOWNORMAL = 1
    SW_NORMAL = 1
    SW_SHOWMINIMIZED = 2
    SW_SHOWMAXIMIZED = 3
    SW_MAXIMIZE = 3
    SW_SHOWNOACTIVATE = 4
    SW_SHOW = 5
    SW_MINIMIZE = 6
    SW_SHOWMINNOACTIVE = 7
    SW_SHOWNA = 8
    SW_RESTORE = 9
    SW_SHOWDEFAULT = 10
    SW_FORCEMINIMIZE = 11
    SW_MAX = 11
  End Enum
  <StructLayout(LayoutKind.Sequential, Pack:=4)> _
  Public Structure PAINTSTRUCT
    Public hdc As IntPtr
    Public fErase As Integer
    Public rcPaint As RECT
    Public fRestore As Integer
    Public fIncUpdate As Integer
    <MarshalAs(UnmanagedType.ByValArray, SizeConst:=32)> _
    Public rgbReserved As Byte()
  End Structure
  <DllImport("user32.dll")> _
  Public Function BeginPaint( _
     ByVal hWnd As IntPtr, ByRef lpPaint As PAINTSTRUCT) As IntPtr
  End Function
  <DllImport("user32.dll")> _
  Public Function EndPaint( _
     ByVal hWnd As IntPtr, ByRef lpPaint As PAINTSTRUCT) As IntPtr
  End Function
  <DllImport("user32.dll")> _
  Public Function GetDC( _
     ByVal hWnd As IntPtr) As IntPtr
  End Function
  <DllImport("user32.dll")> _
  Public Function ReleaseDC( _
     ByVal hWnd As IntPtr, hdc As IntPtr) As IntPtr
  End Function
  <DllImport("user32.dll")> _
  Public Function FillRect(ByVal hDC As IntPtr, ByRef lpRect As RECT, ByVal hBR As IntPtr) As IntPtr
  End Function
  <DllImport("user32.dll")> _
  Public Function InvalidateRect(ByVal hWnd As IntPtr, ByRef lpRect As RECT, ByVal bErase As Boolean) As IntPtr
  End Function
  <DllImport("user32.dll")> _
  Public Function ValidateRect(ByVal hWnd As IntPtr, ByRef lpRect As RECT) As Boolean
  End Function
  <DllImport("user32.dll")> _
  Public Function GetUpdateRect(ByVal hWnd As IntPtr, ByRef lpRect As RECT, ByVal bErase As Boolean) As Boolean
  End Function
  <DllImport("gdi32.dll")> _
  Public Function Ellipse(ByVal hDC As IntPtr, nLeft As Integer, nTop As Integer, nRight As Integer, nBottom As Integer) As IntPtr
  End Function
  <DllImport("gdi32.dll")> _
  Public Function CreateSolidBrush(ByVal crColor As IntPtr) As IntPtr
  End Function

  <DllImport("gdi32.dll")> _
  Public Function DeleteObject(ByVal hObject As IntPtr) As IntPtr
  End Function
  <DllImport("gdi32.dll")> _
  Public Function SelectObject(hDC As IntPtr, ByVal hObject As IntPtr) As IntPtr
  End Function
  <DllImport("gdi32.dll")> _
  Public Function MoveToEx(hDC As IntPtr, X As Integer, Y As Integer, ByRef lpPointPrev As POINT) As Boolean
  End Function
  <DllImport("gdi32.dll")> _
  Public Function LineTo(hDC As IntPtr, nXEnd As Integer, nYEnd As Integer) As Boolean
  End Function

#End Region

End Module

</code>