I am making a WPF application for quoting, and in one of my windows (defined as a user control, not a window), I have a print button that successfully renders the window to a bitmap and then into an image and goes into a print preview screen. I coded this all, but now I have to print other windows in the print page.
So I wrote this function that works for the current window
Private Function RenderWindowToBitmap(userControl As UserControl, scaleFactor As Double) As System.Drawing.Image
Dim parentWindow As Window = Window.GetWindow(userControl)
If parentWindow Is Nothing Then
Throw New InvalidOperationException("Unable to retrieve the parent window of the specified user control.")
End If
Dim rtb As New RenderTargetBitmap(CInt(parentWindow.ActualWidth * scaleFactor), CInt(parentWindow.ActualHeight * scaleFactor), 96, 96, PixelFormats.Pbgra32)
Dim visual As New DrawingVisual()
Using dc As DrawingContext = visual.RenderOpen()
dc.PushTransform(New ScaleTransform(scaleFactor, scaleFactor))
dc.DrawRectangle(New VisualBrush(parentWindow), Nothing, New Rect(New Size(parentWindow.ActualWidth, parentWindow.ActualHeight)))
End Using
rtb.Render(visual)
Dim png As New PngBitmapEncoder()
png.Frames.Add(BitmapFrame.Create(rtb))
Dim stream As New MemoryStream()
png.Save(stream)
Return System.Drawing.Image.FromStream(stream)
End Function
And when it's called for the current window it works perfectly and looks like this
bitmaps.Add(RenderWindowToBitmap(Me, scaleFactor))
But now I want to throw other user controls into this function. My print function exists in this user control's xaml.vb file (called General.xaml.vb), so I just need the instances of the other user controls to get this print to work. I have a main view model that is passed in the constructor for this page, and the viewmodels are required to make a new instance of its respective User Control.
So here is my RollEnd.xaml.vb class that is one of the user controls I need:
Partial Public Class RollEnd
Inherits UserControl
Public Sub New(viewModel As RollEndViewModel)
InitializeComponent()
Me.DataContext = viewModel
End Sub
'Private Sub ComboBox_Loaded(sender As Object, e As RoutedEventArgs)
' Dim comboBox As ComboBox = CType(sender, ComboBox)
' Dim dataContext = comboBox.DataContext
'End Sub
End Class
My General.xaml.vb constructor
Public Sub New(viewModel As MainViewModel)
InitializeComponent()
_mainViewModel = viewModel
_quoteViewModel = viewModel.QuoteViewModel
Me.DataContext = _quoteViewModel
End Sub
As you can see, my General UserControl has the mainViewModel passed in which contains the RollEndViewModel, so I don't understand why when I run my code, the instances of the other User Controls are null.
I tried making the UserControls properties of my MainViewModel, and I was hoping that by instantiating them in the constructor of my mainViewModel, they would be filled on load. But nope they were null. I tried traversing the Visual Tree of User Controls with this function but it just returns nothing
Public Function FindUserControl(Of T As UserControl)(parent As DependencyObject) As T
If parent Is Nothing Then Return Nothing
For i As Integer = 0 To VisualTreeHelper.GetChildrenCount(parent) - 1
Dim child As DependencyObject = VisualTreeHelper.GetChild(parent, i)
If TypeOf child Is T Then
Return CType(child, T)
End If
Dim result As T = FindUserControl(Of T)(child)
If result IsNot Nothing Then
Return result
End If
Next
Return Nothing
End Function
I also tried making the User Controls properties in my MainWindow.xaml.vb class like this
This line below is in the constructor for the main window
_rollEndControl = New RollEnd(MainViewModel.Instance.RollEndViewModel)
_rollEndControl is a property with this getter in my MainWindow.xaml.vb:
Public Function GetRollEndControl() As RollEnd
Return _rollEndControl
End Function
and when I call this function in General.xaml.vb it returns nothing.
Sorry this seems like a complex issue, but I am hoping if anyone here knows a simple way to just get access to an instance of the userControl: thanks!
Also for reference my MainViewModel is a singleton Lazy initialization.
I ended up solving this problem by making all the user controls implement an interface I created called IPrintable.
So now my code looks like this
Partial Public Class RollEnd
Inherits UserControl
Implements IPrintable
Public ReadOnly Property IPrintable_Loaded As Task(Of UserControl) Implements IPrintable.Loaded
Get
Dim tcs As New TaskCompletionSource(Of UserControl)()
Dim handler As RoutedEventHandler = Nothing
handler = Sub(sender, e)
RemoveHandler Me.Loaded, handler
tcs.SetResult(Me)
End Sub
AddHandler Me.Loaded, handler
Return tcs.Task
End Get
End Property End Class
I also created the interface that looks like this
Public Interface IPrintable
ReadOnly Property Loaded As Task(Of UserControl)
End Interface
So I did it this way so the pages would render for printing in my application. Really the instances of the user controls were null because they literally weren't rendered. Really the only code that was hard to understand was the IPrintable_Loaded property that essentially is converting an event into a task. The reason for this is so we can use async. That way the pages in my application can all load smoothly.
The actual implementation for anyone interested for my case looks like this:
Dim bitmaps As New List(Of System.Drawing.Image)
General_Click(Nothing, Nothing)
Dim UC As IPrintable = MainContent.Content
Await UC.Loaded
bitmaps.Add(RenderWindowToBitmap(MainContent.Content, scaleFactor))
Create a list of pages I want to print (bitmaps), call a method that literally just makes the MainContent.Content as the "General" user control (I have 4 different user controls), and then I create a variable UC (user control) that is IPrintable that is set to the mainContent and then we await for that task to finish and render the window. I repeat this code 4 times for all my user controls.
I doubt anyone would have this specific problem, but if you ever need to access an instance of a user control in WPF, using an interface of some kind might help you do it!