Handling Events For Controls Added At Run Time - wpf

I am adding controls at run time in a WPF App, these are all different types of control so are passed as a UIElement. I then get the type of control and can set the properties using SetValue. As I have a lot of control types to implement what I am now trying to do is is add an event to each control but can only seem to do this using Addhandler which requires a lot of extra code for each control as shown below. I am looking for a solution to allow me to add the error handler to the UIElement without prior conversion using a Cast, I am not sure this is possible?
Select Case Control.GetType()
Case GetType(ExtendedTextBox)
Dim newControl As ExtendedTextBox = TryCast(Control, ExtendedTextBox)
'Dim newCtl As UIElement = TryCast(Control, ExtendedTextBox)
If newControl IsNot Nothing Then
newControl.SetValue(ExtendedTextBox.HorizontalAlignmentProperty, HorizontalAlignment.Left)
newControl.SetValue(ExtendedControlProperties.HighlightTextOnFocusProperty, True)
newControl.SetValue(ExtendedControlProperties.MandatoryProperty, True)
AddHandler newControl.HasErrors, AddressOf UpdateErrorStatus
End If

I was incorrectly prefixing my validation with "Customer." which as in inherited control was causing the issue, once removed it works as expected.

Related

WPF DataGrid Double-Click Exception

Long-time WinForms programmer relatively new to WPF. I'm databinding a DataGrid in Code-Behind using a SQL Query. I reuse the DataGrid because I'm using Ribbon Tabs and reload different data in the grid dependent on the Tab selected. So binding to a static resource is not possible.
I'm trying to open a new window on a double-click event but get the following exceeption - "Unable to cast object of type 'System.Windows.RoutedEventArgs' to type 'System.Windows.Input.MouseButtonEventArgs'."
This used to be a simple thing in WinForms. My code is as follows:
Try
Dim StrRow As DataRowView = MainDataGrid.SelectedItem
Dim CellValue As String = StrRow.Row(0).ToString()
'MsgBox(CellValue)
e.Handled = True
Dim EventDetails = New EventDetails()
EventDetails.Show()
OpenEventDetailsWindow()
Catch ex As Exception
MsgBox("No Event No.for this Event")
End Try
The messagebox shows the proper string (first cell value of selected row and the new window actually pops up right before the exception is thrown. I've seen plenty of posts that say this method should work but it doesn't. I've been so far unsuccessful in fixing this. Thanks in advance.

How do I change the style of FindTextBox of FlowDocumentReader to match the theme of my app?

I have a WPF app with a FlowDocumentReader control and when I click the search button to search the text, the text box doesn't match the theme of the app. The text does, but the background of the text box doesn't. So if I am using a dark theme, the text I type into the text box is white (which is correct), but the text box background is also white (incorrect, making text not legible).
Does anyone know how to fix this? I tried applying a style but I don't know which component to target.
Wow, Microsoft really does not make this easy.
My Process
I tried the easy trick of adding a Style TargeType="TextBox" to FlowDocumentReader.Resources, but that doesn't work.
I tried doing things the "right" way and overriding FlowDocumentReader's ControlTemplate, but the TextBox in question isn't even part of the ControlTemaplte! Instead, there's a Border named PART_FindToolBarHost. The TextBox we want is added as a child to PART_FindToolBarHost in code- but only after the user has clicked the "find" button (the one with the magnifying glass icon). You can see this for yourself by looking at the control's source code.
With no more XAML-only ideas, I had to resort to using code. We need to somehow get a reference to the TextBox being created, and I can't think of any better way than to manually search the visual tree for it. This is complicated by the fact that the TextBox only exists once the find command has been executed.
Specifically, the find button in FlowDocumentReader binds to ApplicationCommands.Find. I tried adding a CommandBinding for that command to FlowDocumentReader so I could use it as a trigger to retrieve the TextBox. Unfortunately, adding such a CommandBinding somehow breaks the built-in functionality and prevents the TextBox from being generated at all. This breaks even if you set e.Handled = False.
Luckily, though, FlowDocumentReader exposes an OnFindCommand- except, of course, it's a Protected method. So I finally gave in and decided to inherit FlowDocumentReader. OnFindCommand works as a reliable trigger, but it turns out the TextBox isn't created until after the sub finishes. I was forced to use Dispatcher.BeginInvoke in order to schedule a method to run after the TextBox was actually added.
Using OnFindCommand as a trigger, I was finally able to reliably get a reference to the "find" TextBox, which is actually named FindTextBox.
Now that I can get a reference, we can apply our own Style. Except: FindTextBox already has a Style, so unless we want to override it, we're going to have to merge the two Styles. There's no publicly-accessible method for this (even though WPF does this internally in places), but luckily I already had some code for this.
The Working Code
First, a Module with the helper methods I used:
FindVisualChild is used to loop through the visual tree and get a reference to FindTextBox.
MergeStyles is used to combine the existing Style with the Style we supply, once we have that reference.
Module OtherMethods
<Extension()>
Public Function FindVisualChild(obj As DependencyObject, Name As String) As FrameworkElement
For i As Integer = 0 To VisualTreeHelper.GetChildrenCount(obj) - 1
Dim ChildObj As DependencyObject = VisualTreeHelper.GetChild(obj, i)
If TypeOf ChildObj Is FrameworkElement AndAlso DirectCast(ChildObj, FrameworkElement).Name = Name Then Return ChildObj
ChildObj = FindVisualChild(ChildObj, Name)
If ChildObj IsNot Nothing Then Return ChildObj
Next
Return Nothing
End Function
Public Function MergeStyles(ByVal style1 As Style, ByVal style2 As Style) As Style
Dim R As New Style
If style1 Is Nothing Then Throw New ArgumentNullException("style1")
If style2 Is Nothing Then Throw New ArgumentNullException("style2")
If style2.BasedOn IsNot Nothing Then style1 = MergeStyles(style1, style2.BasedOn)
For Each currentSetter As SetterBase In style1.Setters
R.Setters.Add(currentSetter)
Next
For Each currentTrigger As TriggerBase In style1.Triggers
R.Triggers.Add(currentTrigger)
Next
For Each key As Object In style1.Resources.Keys
R.Resources(key) = style1.Resources(key)
Next
For Each currentSetter As SetterBase In style2.Setters
R.Setters.Add(currentSetter)
Next
For Each currentTrigger As TriggerBase In style2.Triggers
R.Triggers.Add(currentTrigger)
Next
For Each key As Object In style2.Resources.Keys
R.Resources(key) = style2.Resources(key)
Next
Return R
End Function
End Module
Then, there's StyleableFlowDocumentReader, which is what I named my extended control that inherits FlowDocumentReader:
Public Class StyleableFlowDocumentReader
Inherits FlowDocumentReader
Protected Overrides Sub OnFindCommand()
MyBase.OnFindCommand()
Dispatcher.BeginInvoke(Sub() GetFindTextBox(), DispatcherPriority.Render)
End Sub
Private Sub GetFindTextBox()
findTextBox = Me.FindVisualChild("FindTextBox")
ApplyFindTextBoxStyle()
End Sub
Private Sub ApplyFindTextBoxStyle()
If findTextBox IsNot Nothing Then
If findTextBox.Style IsNot Nothing AndAlso FindTextBoxStyle IsNot Nothing Then
findTextBox.Style = MergeStyles(findTextBox.Style, FindTextBoxStyle)
Else
findTextBox.Style = If(FindTextBoxStyle, findTextBox.Style)
End If
End If
End Sub
Private findTextBox As TextBox
Public Property FindTextBoxStyle As Style
Get
Return GetValue(FindTextBoxStyleProperty)
End Get
Set(ByVal value As Style)
SetValue(FindTextBoxStyleProperty, value)
End Set
End Property
Public Shared ReadOnly FindTextBoxStyleProperty As DependencyProperty =
DependencyProperty.Register("FindTextBoxStyle",
GetType(Style), GetType(StyleableFlowDocumentReader),
New PropertyMetadata(Nothing, Sub(d, e) DirectCast(d, StyleableFlowDocumentReader).ApplyFindTextBoxStyle()))
End Class
And then, finally, a usage example:
<local:StyleableFlowDocumentReader x:Name="Reader">
<local:StyleableFlowDocumentReader.FindTextBoxStyle>
<Style TargetType="TextBox">
<Setter Property="Foreground" Value="Blue"/>
</Style>
</local:StyleableFlowDocumentReader.FindTextBoxStyle>
<FlowDocument/>
</local:StyleableFlowDocumentReader>

Internet Explorer instance DocumentComplete not firing

I am trying to create an instance of Internet Explorer from a WPF application, load a saved local file, and do some further processing once the file is loaded. However, although the file is visible in the Internet Explorer window, the DocumentComplete event never fires:
'static field
Dim iex As ShDocVw.InternetExplorer
Public Sub DoStuff()
Dim path = "c:\test.htm"
iex = New SHDocVw.InternetExplorer
iex.Visible = True
AddHandler iex.DocumentComplete, Sub(o As Object, ByRef url As Object)
'This code is never executed
Dim i = 5
End Sub
iex.Navigate2(path)
End Sub
When I navigate to a non-local URL (e.g. http://www.google.com) the DocumentComplete event does fire.
The same behavior exists for the NavigateComplete2 event.
I tried using a class member method instead of a lambda expression (maybe the lambda expression is going out of scope once the method exits?) using both AddressOf and Handles, but that didn't help.
What do I have to do to have the DocumentComplete event fire?
(NB: The page has no frames.)
Update
This code is being used in a class library, and I therefore cannot use the WebBrowser control, as it cannot be instantiated in code.
As SimonMourier points out in the comments, a WebBrowser can be instantiated in code:
Dim wb = New WebBrowser
AddHandler wb.LoadCompleted, Sub(s, e)
Dim i = 5
End Sub
wb.Navigate(path)
Although the LoadCompleted event still doesn't fire, the Navigated event does, and it appears to be sufficient for my purposes. (Apparently the WebBrowser has to be visible in order for LoadCompleted to fire -- see here and here -- and since I am not using the WebBrowser in the context of a window, I don't think this is even possible in my case.)
You should use the out-of-the-box standard WebBrowser Control that ships with WPF (there is another one for Winforms apps). It has all the basic events directly supported.
Should you miss some Winforms feature like IsWebBrowserContextMenuEnabled or ScriptErrorsSuppressed, I suggest you refer to my answer in this question on SO: How to deactivate "right click" on WPF Webbrowser Control?
You only need interop in these special cases or if you need to get ahold on the native underlying IE's Document Object Model (DOM), interfaces like IHTMLDocument2, etc.
Instead of using ShDocVw.InternetExplorer you can use WebBrowser control provided by WPF:
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525" Loaded="Window_Loaded">
<Grid>
<WebBrowser x:Name="webBrowser" Visibility="Visible" />
</Grid>
</Window>
Class MainWindow
Public Sub DoStuff()
Dim path = New Uri("c:\test.htm")
AddHandler webBrowser.LoadCompleted, Sub(sender As Object, e As System.Windows.Navigation.NavigationEventArgs)
Dim i = 5
End Sub
webBrowser.Navigate(path)
End Sub
Private Sub Window_Loaded(sender As Object, e As RoutedEventArgs)
DoStuff()
End Sub
End Class
There is no need to use WebBrowser Control to get this issue resolved.
I have also faced this issue and it happens due to the access privileges.
Run your application with Admin privileges and it will be fine.
To debug try running your Visual Studio as Administrator and then test, The DocumentComplete event will be fired.
Update1:
In case of non-admin application if you can manage to get the internet explorer started with Admin privileges then also you can work with it using your non-admin application.
Simple start a Internet Explorer process with admin privileges.
Then you can hook it using this code
For Each IE As InternetExplorer In New SHDocVw.ShellWindows
If IE.FullName.ToLower.Contains("iexplore") And IE.LocationURL <> "" Then
'Capture IE here
End If
Next

Loading XAML content without new window

I'm trying to find a way to change the content of a window in WPF without having to load a new window. In most cases, the following works just fine:
dim x as new window
x.show()
me.close
Is there a way to do something like the following?
dim x as new window
me.content = x
This answer is a bit delayed. I ran into a situation where I needed to change all or part of the contents of a window, so as suggested by wpf-it - I used ContentControl.
The window I used this in is being used with a barcode scanner. We have a handful of item types that can be scanned with a different set of options and displays needed for each. Since our app is written in the MVVM pattern (Or at least... MVVM-esque ;) ) I created some XAML:
<ContentControl Grid.Row="2" Content="{Binding itemOptions}" />
Which is then bound to this property:
Public ReadOnly Property itemOptions As UserControl
Get
Select Case SearchResult.GetType()
' Part
Case GetType(partHeaderModel)
Try
Return New partOptions
Catch ex As Exception
' Fail
' Return New noResults
End Try
' Bin
Case GetType(binModel)
Try
Return New binOptions
Catch ex As Exception
' Fail
Return New noResults
End Try
' Rack
Case GetType(rackModel)
Try
Return New rackOptions
Catch ex As Exception
' Fail
Return New noResults
End Try
Case Else
End Select
Return New noResults
End Get
End Property
I have a search function which returns a data model based on the type of item the user scans. Based on the type, the select statement returns the property user control. I use INotifyPropertyChanged along with the scan event to get the window to update.
Check this topic: How to get reference to element in resources, WPF?
Read, that it can help .. There are thousands of ways to work, so you must find his or better.
An old concept which was used is the MDI:
http://www.codeproject.com/Articles/22927/Multiple-Window-Interface-for-WPF
How about setting all the possible configurations of your windows in one window, and toggling the visibilities of whatever you want to show and not show as and when needed?
That will make it easier to customize rather than manually modifying the Visual Tree. However remember to keep any panels virtualized or your performance could take a hit.
Did you explore ContentPresenter / ContentControl option in WPF? Keep a content presenter / content control in your window, and set their Content property dynamically... this way you will not need to reload the window.

Programatically setting design-time properties in Windows.Forms control

Is there an easy way to programatically setting a property value on a control such that it will be persisted in the designer-generated code?
I imagine a piece of code in the control constructor or load event which is executed when i open the control in design mode, but sets a property such that it will be persisted the same way as if I changed the value manually through the properties grid.
Edit: Yes, this would be the same as editing the designer code manually, but I want to do it programatically.
Presuming I understand the question
You can databind that property to a setting, using the Visual studio Gui. Check the properties for that control, under the Data section for (Application Settings), (Property Bindings).
It depends on what kind of functionality you want. If you only need the properties to be set when you add the control to a form, then setting the properties in the control's constructor works perfectly. However, changes you make using the Properties panel will take precedence, and setting properties in the control's constructor won't necessarily affect existing instances of the control.
If you want to be able to change the properties for instances of the control in one place, assigning bindings in (application settings), (property bindings) works. Then you can modify all the bindings from the Settings.settings file. This still requires you to assign property bindings for each instance of the control, though.
Now for the finale. If you want to set properties in the control's class that affect all instances of the control, whether the instances are yet to be created or already exist, you have to get a little creative. I found a solution, but it may not be the best. My solution goes like this:
In the control's constructor, for each property you want to set, you:
Store the desired value in a private variable.
Assign the variable's value to the property.
Assign an event handler that assigns the variable's value to the property whenever the property is changed.
A downside is the amount of coding for each property. Also, you wouldn't be able to change the properties from the Properties pane.
Do you think about something like:
if (this.DesignMode)
{
// do somthing
}
If you put this into the constructor remember to call InitializeComponent() before.
What about:
Private Function GetPropertyByName(ByVal propName As String) _
As PropertyDescriptor
Dim prop As PropertyDescriptor
prop = TypeDescriptor.GetProperties(l_dWindow)(propName)
If prop Is Nothing Then
Throw New ArgumentException( _
"Matching ColorLabel property not found!", propName)
Else
Return prop
End If
End Function
Private Sub btnOK_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnOK.Click
GetPropertyByName("AnyPublicProperty").SetValue(AnyControl, "AnyStringVALUE")
Me.DialogResult = DialogResult.OK
End Sub

Resources