Handling events from user control containing a WPF textbox - wpf

In order to take advantage of the spell checking ability of WPF textboxes, I have added one to a user control (with the use of elementhost). This user control is used in various window forms. My current problem is trying to handle keyup events from this textbox but the windows form is unable to "get" any event from the control. I can access the properties of the textbox just fine (i.e. text, length, etc.) but keyboard events don't seem to work.
I have found, however, that the following will bring back events from the WPF textbox:
Public Class MyUserControl
Private _elementHost As New ElementHost
Private _wpfTextbox As New System.Windows.Controls.Textbox
Private Sub MyUserControl_Load(...) Handles Me.Load
Me.Controls.Add(_elementHost)
_elementHost.Dock = DockStyle.Fill
_elementHost.Child = _wpfTextbox
Dim MyEventInfo As EventInfo
Dim MyMethodInfo As MethodInfo
MyMethodInfo = Me.GetType().GetMethod("WPFTextbox_KeyUp")
MyEventInfo = _wpfTextBox.GetType().GetEvent("PreviewKeyUp")
Dim dlg As [Delegate] = [Delegate].CreateDelegate(MyEventInfo.EventHandlerType, Me, MyMethodInfo)
MyEventInfo.AddEventHandler(_wpfTextBox, dlg)
End Sub
Public Sub WPFTextbox_KeyUp(ByVal sender As Object, ByVal e As RoutedEventArgs)
' something goes here
End Sub
End Class
The user control is now able to do something after the PreviewKeyUp event is fired in the WPF textbox. Now, I'm not completely sure how to have the window form containing this user control to work with this.

Im a C# person not VB so please bear with me.. Basically you could assign the event from your Window rather than within your UserControl.. So in the constructor of your Window assign the PreviewKeyUp:
this.myUserContorl.PreviewKeyUp += new System.Windows.Input.KeyEventHandler(WPFTextbox_KeyUp);
then place the event handler in your Window:
private void WPFTextbox_KeyUp(object sender, System.Windows.Input.KeyEventArgs e)
{
}
Incidentally you needn't go through the hassle of capturing the event in your UserControl as you can still access your TextBox within you UserControl directly from your Window (if you make it public), again from your constructor in your Window:
this.myUserContorl.wpfTextbox.PreviewKeyUp += new System.Windows.Input.KeyEventHandler(WPFTextbox_KeyUp);
I imagine it would look like this in VB (at a guess):
AddHandler myUserContorl.wpfTextbox.PreviewKeyUp, AddressOf WPFTextbox_KeyUp

ElementHost has a static method called EnableModelessKeyboardInterop(). Try calling it?
ElementHost.EnableModelessKeyboardInterop();
Read more here

Seems basic, but did you set the KeyPreview to TRUE on your form?

Related

WPF Can I use a DataTrigger to have the View do something after the View Model changes the value of a Property?

My WPF MVVM VB.NET app loads a list of songs into a ListBox at start. The list contents populate in a BackgroundWorker that is kicked off in the Constructor of the ViewModel. Once this is done, I want to set focus to the first song in the list.
As setting this focus is purely a View operation, I want it in the code-behind of the XAML. It's no business of the ViewModel where focus goes.
I tried doing this on various Window and ListBox events, but they either don't fire, or fire too early. So I'm think what I need is a Boolean Property that the ViewModel sets when it's done loading the songs into the list. That's when I need the View to catch that Property Change, and call the code-behind function that has the logic to maniuplate the View, in the is case, setting focus on the first song in the list.
But this is where my knowledge of WPF is short. I searched and it sounds like DataTrigger could do the trick. But where to put it, and what's the right syntax, and how to have it call my code-behind function?
Or is there an even simpler way that I'm overlooking. This seems like a basic functionality - to trigger some code-behind action in the View when a Property changes a certain way in the ViewModel.
Here's the code-behind function. I can elaborate it once it's successfully getting called at the intended time:
Private Sub FocusSongsList()
' set focus back to the Songs list, selected item (couldn't just set focus to the list, it ran forever and looks like it set focus to every item in turn before releasing the UI)
Dim listBoxItem = CType(LstSongs.ItemContainerGenerator.ContainerFromItem(LstSongs.SelectedItem), ListBoxItem)
If Not listBoxItem Is Nothing Then
listBoxItem.Focus()
End If
End Sub
Here's my ListBox:
<ListBox x:Name="LstSongs" ItemsSource="{Binding FilteredSongs}" DisplayMemberPath="Path"
HorizontalAlignment="Stretch"
SelectionMode="Extended" SelectionChanged="LstSongs_SelectionChanged" Loaded="FocusSongsList"/>
And I would define a new property that can be set from the RunWorkerCompleted part of the BackgroundWorker.
Private _InitialSongLoadCompleted As Boolean
Public Property InitialSongLoadCompleted() As Boolean
Get
Return _InitialSongLoadCompleted
End Get
Set(ByVal value As Boolean)
_InitialSongLoadCompleted = value
RaisePropertyChanged("InitialSongLoadCompleted")
End Set
End Property
A DataTrigger can't execute methods. It can only set properties.
Focus can't be activated by setting a property, therefore a DataTrigger can't solve your problem.
Generally, if you have longrunning initialization routines you should move them to an init routine (which could be async) or use Lazy<T>.
For example, you instantiate your view model class and call Initialize() afterwards. After the method has returned you can continue to initialize the ListBox:
MainWindow.xaml.cs
Partial Class MainWindow
Inherits Window
Private ReadOnly Property MainViewModel As MainViewModel
Public Sub New(ByVal dataContext As TestViewModel, ByVal navigator As INavigator)
InitializeComponent()
Me.MinViewModel = New MainViewMdel()
Me.DataContext = Me.MainViewModel
AddHandler Me.Loaded, AddressOf OnLoaded
End Sub
' Use the Loaded event to call async methods outside the constructor
Private Async Sub OnLoaded(ByVal sender As Object, ByVal e As EventArgs)
Await mainViewModel.InitializeAsync()
' For example handle initial focus
InitializeListBox()
End Sub
End Class
MainViewModel.cs
Class MainViewModel
Inherits INotifyPropertyChanged
Public Async Function InitializeAsync() As Task
Await Task.Run(AddressOf InitializeSongList)
End Function
Private Sub InitializeSongList()
' TODO::Initialize song list
End Sub
End Class
It has been a long long time since I wrote much VB, so I'm afraid this is c# code.
You can handle targetupdated on a binding.
This fires when data transfers from the source ( viewmodel property ) to the target (the ui property and here itemssource)
<ListBox
x:Name="LstSongs"
ItemsSource="{Binding Songs, NotifyOnTargetUpdated=True}"
TargetUpdated="ListBox_TargetUpdated"/>
When you replace your list, that targetupdated will fire.
If you raise property changed then the data will transfer ( obviously ).
private async void ListBox_TargetUpdated(object sender, DataTransferEventArgs e)
{
await Task.Delay(200);
var firstItem = (ListBoxItem)LstSongs.ItemContainerGenerator
.ContainerFromItem(LstSongs.Items[0]);
firstItem.Focus();
Keyboard.Focus(firstItem);
}
As that data transfers, there will initially be no items at all of course so we need a bit of a delay. Hence that Task.Delay which will wait 200ms and should let the UI render. You could make that a bit longer or dispatcher.invokeasync.
It finds the first container and sets focus plus keyboard focus.
It might not be at all obvious that item has focus.
A more elegant approach using dispatcher will effectively schedule this focussing until after the ui has rendered. It might, however, look rather tricky to someone unfamiliar with c#
private void ListBox_TargetUpdated(object sender, DataTransferEventArgs e)
{
Dispatcher.CurrentDispatcher.InvokeAsync(new Action(() =>
{
var firstItem = (ListBoxItem)LstSongs.ItemContainerGenerator
.ContainerFromItem(LstSongs.Items[0]);
firstItem.Focus();
Keyboard.Focus(firstItem);
}), DispatcherPriority.ContextIdle);
}
If you want a blue background then you could select the item.
firstItem.IsSelected = true;
Or you could use some datatrigger and styling working with IsFocused.
https://learn.microsoft.com/en-us/dotnet/api/system.windows.uielement.isfocused?view=windowsdesktop-7.0
( Always distracts me that, one s rather than two and IsFocussed. That'll be US english I gues )
Here's my mainwindowviewmodel
public partial class MainWindowViewModel : ObservableObject
{
[ObservableProperty]
private List<string> songs = new List<string>();
MainWindowViewModel()
{
Task.Run(() => { SetupSongs(); });
}
private async Task SetupSongs()
{
await Task.Delay(1000);
Songs = new List<string> { "AAA", "BBB", "CCC" };
}
}
I'm using the comunity toolkit mvvm for code generation. Maybe it does vb as well as c#.
You might accomplish your goal by defining a custom event in the viewmodel which is raised when the list processing is complete. The view can subscribe to it and act accordingly.
It would look something like this:
Class MyViewModel
'Custom eventargs shown for completeness, you can use EventHandler if you
'don't need any custom eventargs.
Public Event ListCompleted As EventHandler(Of ListCompletedEventArgs)
'...
Public Sub ProcessSongList()
'Note that if this runs on a background thread, you may need to
'get back on the UI thread to raise an event for the view to handle
RaiseEvent ListCompleted(Me, New ListCompletedEventArgs())
End Sub
End Class
Class MyView
Public Sub New(ByVal vm as MyViewModel)
Me.DataContext = vm
AddHandler vm.ListCompleted, AddressOf OnListCompleted
End Sub
Private Sub OnListCompleted(ByVal sender As Object, ByVal args As ListCompletedEventArgs)
'...
End Sub
'...
End Class
You mentioned doing processing on a background thread. I'm not completely sure which thread the completion event would issue into, but beware that UI stuff can only happen on the UI thread so you might need to use a Dispatcher.Invoke to make sure your code runs on the right thread. I'd do it to run the RaiseEvent so the view doesn't need to know anything about it.

What is the correct Xaml syntax (and underlying vb code) to have a event raised by a custom Control handled in a host application view model

I have a wpf Custom Control which is now raising custom events. In a test project that I have created to test the ongoing development of the control I have placed a copy of my new custom control on the main window.
In the properties dialog for said control I can see my new event and I can type in the name for a handler for it in the code behind the main window and it's not only created for me but when I place a break point on it I see both the sender and my own event args raised in the control.
What I would now like to know Is what is the correct way to alert a viewmodel about that event respecting the principles of MVVM.
Below is the xaml markup for my custom control in the mainwindow of the test application
<WpfControls:VtlDataNavigator Name="vtlDn"
ItemsSource="{Binding Path=Orders}"
Grid.Row="1"
AddButtonVisibility="True"
CancelNewRecordButtonVisibility="True"
SaveButtonVisibility="True" />
The event I want to catch is called 'RecordControlButtonClicked' and this is the event automatically created in the mainwindow code behind
Private Sub MyCustomHandlerForControlEvent(sender As Object, e As ViewToLearn.WpfControls.VtlDataNavigatorEventArgs) Handles vtlDn.RecordControlButtonClicked
End Sub
So now what I'm after is the correct syntax in both the xaml and my new MainWindowViewModel to have this event transferred across to the viewmodel for handling (or suggestions as to the most efficient way to do this idf there is no direct route.
FYI I already have a relay command set up in the test application but the way that I've used it to bind commands in the past doesn't appear to be working with events.
Edit
One approach I've tried which appears to work is to add the following in the code behind of the main window's loaded event.
Class MainWindow
Public Sub New()
InitializeComponent
DataContext = New MainWindowViewModel
End Sub
Private Sub MainWindow_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
Dim mvm As MainWindowViewModel = DirectCast(DataContext, MainWindowViewModel)
mvm.vtldn = vtlDn
End Sub
End Class
and then add the following in the viewmodel;
Private _vtldn As ViewToLearn.WpfControls.VtlDataNavigator
Public Property vtldn As ViewToLearn.WpfControls.VtlDataNavigator
Get
Return _vtldn
End Get
Set(ByVal Value As ViewToLearn.WpfControls.VtlDataNavigator)
If (_vtldn Is Value) Then Return
If Not IsNothing(vtldn) Then
RemoveHandler vtldn.RecordControlButtonClicked, AddressOf MyCustomHandler
End If
_vtldn = Value
If Not IsNothing(vtldn) Then
AddHandler vtldn.RecordControlButtonClicked, AddressOf MyCustomHandler
End If
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(NameOf(vtldn)))
End Set
End Property
Private Sub MyCustomHandler(ByVal sender As Object, ByVal e As VtlDataNavigatorEventArgs)
'this does get called when buttons in the custom control are clicked
End Sub
However I'm acutely aware that this is not really 'pure' mvvm so if there are better ways to do this I'm open to suggestions.

How do I improve performance of WPF UserControl rendered in Windows Forms

We are using wpf user controls in windows form like data entry screen with more than 100 Controls. The form rendering time increased after continue usage. Any suggestions?
In Xaml part:
Grid Panel to arrange the controls
Windows form calling method:
Public form As New WPFUserControl
form.Tag = Me
ElementHost1.Child = form
form.Formload()
-- more code from comments --
Private Sub frmSwitch_New_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
Public form As New WPFUserControl
form.Tag = Me ElementHost1.Child = form
form.Formload()
End Sub
You are creating instance of you 100s control everytime you call
frmSwitch_New_Load
but you never unload new controls created. Just when your frmSwich unloads you need to unload your wpf controls as well
Where you give a call to frmSwitch_New_Load if that's a Load event handler to your form, have an event handler for unload event to that form and underthat event handler unload your current wpf control

Apply and validate a bound DataGridViewComboBoxCell directly upon selection change

I have a windows forms DataGridView that contains some DataGridViewComboBoxCells that are bound to a source collection using DataSource, DisplayMember and ValueMember properties. Currently the the combobox cell commits the changes (i.e. DataGridView.CellValueChanged is raised) only after I click on another cell and the combobox cell loses focus.
How would I ideally commit the change directly after a new value was selected in the combobox.
This behaviour is written into the implementation of the DataGridViewComboBoxEditingControl. Thankfully, it can be overridden. First, you must create a subclass of the aforementioned editing control, overriding the OnSelectedIndexChanged method:
protected override void OnSelectedIndexChanged(EventArgs e) {
base.OnSelectedIndexChanged(e);
EditingControlValueChanged = true;
EditingControlDataGridView.NotifyCurrentCellDirty(true);
EditingControlDataGridView.CommitEdit(DataGridViewDataErrorContexts.Commit);
}
This will ensure that the DataGridView is properly notified of the change in item selection in the combo box when it takes place.
You then need to subclass DataGridViewComboBoxCell and override the EditType property to return the editing control subclass from above (e.g. return typeof(MyEditingControl);). This will ensure that the correct editing control is created when the cell goes into edit mode.
Finally, you can set the CellTemplate property of your DataGridViewComboBoxColumn to an instance of the cell subclass (e.g. myDataGridViewColumn.CellTemplate = new MyCell();). This will ensure that the correct type of cell is used for each row in the grid.
I tried using Bradley's suggestion, but it was sensitive to when you attached the cell template. It seemed like I couldn't allow the design view to wire up the column, I had to do it myself.
Instead, I used the binding source's PositionChanged event, and triggered updates from that. It's a little bit odd, because the control is still in edit mode, and the databound object doesn't get the selected value yet. I just updated the bound object myself.
private void bindingSource_PositionChanged(object sender, EventArgs e)
{
(MyBoundType)bindingSource.Current.MyBoundProperty =
((MyChoiceType)comboBindingSource.Current).MyChoiceProperty;
}
A better way to achieve this that I am using successfully rather than subclassing or the somewhat inelegant binding source method above, is the following (sorry it's VB but if you can't translate from VB to C# you have bigger problems :)
Private _currentCombo As ComboBox
Private Sub grdMain_EditingControlShowing(ByVal sender As Object, ByVal e As System.Windows.Forms.DataGridViewEditingControlShowingEventArgs) Handles grdMain.EditingControlShowing
If TypeOf e.Control Is ComboBox Then
_currentCombo = CType(e.Control, ComboBox)
AddHandler _currentCombo.SelectedIndexChanged, AddressOf SelectionChangedHandler
End If
End Sub
Private Sub grdMain_CellEndEdit(ByVal sender As Object, ByVal e As System.Windows.Forms.DataGridViewCellEventArgs) Handles grdMain.CellEndEdit
If Not _currentCombo Is Nothing Then
RemoveHandler _currentCombo.SelectedIndexChanged, AddressOf SelectionChangedHandler
_currentCombo = Nothing
End If
End Sub
Private Sub SelectionChangedHandler(ByVal sender As Object, ByVal e As System.EventArgs)
Dim myCombo As ComboBox = CType(sender, ComboBox)
Dim newInd As Integer = myCombo.SelectedIndex
//do whatever you want with the new value
grdMain.NotifyCurrentCellDirty(True)
grdMain.CommitEdit(DataGridViewDataErrorContexts.Commit)
End Sub
That's it.

Equivalent to a keypreview property in WPF

I'm pondering taking the plunge to WPF from WinForms for some of my apps, currently I'm working on the combined barcode-reader/text-entry program (healthcare patient forms).
To be able to process the barcode characters, I rely on the Keypreview property in WinForms (because barcodes can be scanned regardless of what control has the focus).
But I cannot seem to find a KeyPreview property in neither VS2008 or VS2010, for a WPF app.
Is there an alternative approach/solution to handle my barcode characters in WPF?
Rgrds Henry
use the override in your own UserControls or Controls (this is an override from UIElement)
protected override void OnPreviewKeyDown(System.Windows.Input.KeyEventArgs e) {
base.OnPreviewKeyDown(e);
}
if you want to preview the key down on any element which you dont create you can do this:
Label label = new Label();
label.PreviewKeyDown += new KeyEventHandler(label_PreviewKeyDown);
and then have a handler like so :-
void label_PreviewKeyDown(object sender, KeyEventArgs e) {
}
if you mark the event as handled (e.Handled = true;) this will stop the KeyDown event being raised.
Thanks got it working! Only problem was I'm coding in VB not C#, but the basic idea holds. Neat to create a label out of thin air and use it to insert yourself in the event stream.
If someone else is interested of the same solution but in VB for WPF, here's my test program, it manages to toss all 'a' characters typed, no matter what control has the focus:
Class MainWindow
Dim WithEvents labelFromThinAir As Label
Private Sub Window_Loaded(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles MyBase.Loaded
AddHandler MainWindow.PreviewKeyDown, AddressOf labelFromThinAir_PreviewKeyDown
End Sub
Private Sub labelFromThinAir_PreviewKeyDown(ByVal sender As Object, ByVal e As KeyEventArgs)
TextBox1.Text = e.Key ' watch 'em coming
If (44 = e.Key) Then e.Handled = True
End Sub
End Class
P.S. This was my first post on stackoverflow, really a useful site. Perhaps I'll be able to answer some questions in here myself later on :-)
WPF uses event bubbling and tunneling. In other words the events travel down and up the visual element tree. Some events will have a corresponding Preview event. So MouseDown will have a PreviewMouseDown that you can respond to. Check out this link and scroll down to the WPF Input Events section.

Resources