What's the best way to pass event to ViewModel? - c#

The case is: I have a control's event that I want my ViewModel to react on. Currently I'm doing this by executing a command of invisible button like in the example below.
In View.xaml:
<Control x:Name="SearchResultGrid" ... DataRefreshed="SearchResultRefreshed" />
<Button x:Name="SearchResultRefreshedButton" Visibility="Collapsed" Command="{Binding SearchResultRefreshedCommand}" />
In View.xaml.cs:
private void SearchResultRefreshed(object sender, EventArgs e)
{
if (SearchResultRefreshedButton.Command != null)
{
SearchResultRefreshedButton.Command.Execute(SearchResultGrid.ResultRowCount);
}
}
This works good, but it looks like a hack to me. I'm wondering if there is better (standard) way of doing this? I could not find any examples and this is what I "invented" myself.

Using MVVM, the general way to handle events is to simply wrap them in Attached Properties, or use Attached Events. Here is an example using the PreviewKeyDown event in an Attached Property:
public static DependencyProperty PreviewKeyDownProperty = DependencyProperty.RegisterAttached("PreviewKeyDown", typeof(KeyEventHandler), typeof(TextBoxProperties), new UIPropertyMetadata(null, OnPreviewKeyDownChanged));
public static KeyEventHandler GetPreviewKeyDown(DependencyObject dependencyObject)
{
return (KeyEventHandler)dependencyObject.GetValue(PreviewKeyDownProperty);
}
public static void SetPreviewKeyDown(DependencyObject dependencyObject, KeyEventHandler value)
{
dependencyObject.SetValue(PreviewKeyDownProperty, value);
}
public static void OnPreviewKeyDownChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
TextBox textBox = dependencyObject as TextBox;
if (e.OldValue == null && e.NewValue != null) textBox.PreviewKeyDown += TextBox_PreviewKeyDown;
else if (e.OldValue != null && e.NewValue == null) textBox.PreviewKeyDown -= TextBox_PreviewKeyDown;
}
private static void TextBox_PreviewKeyDown(object sender, KeyEventArgs e)
{
TextBox textBox = sender as TextBox;
KeyEventHandler eventHandler = GetPreviewKeyDown(textBox);
if (eventHandler != null) eventHandler(sender, e);
}
Note that it is just as easy (and better too) to use an ICommand instead of the actual KeyEventArgs object which shouldn't really be in the view model. Just create an Attached Property of type ICommand and call that from this TextBox_PreviewKeyDown handler instead:
private static void TextBox_PreviewKeyDown(object sender, KeyEventArgs e)
{
TextBox textBox = sender as TextBox;
ICommand command = PreviewKeyDownCommand(textBox);
if (command != null && command.CanExecute(textBox)) command.Execute(textBox);
}
Either way, it would be used something like this:
<TextBox TextBoxProperties.PreviewKeyDown="SomeKeyEventHandler" />
Or if you used the preferred ICommand method:
<TextBox TextBoxProperties.PreviewKeyDownCommand="{Binding SomeCommand}" />

Personally I've never had a need to use an attached property to deal with a control's event. In your example, of a control wanting to know when the 'SearchResultRefreshed' and then informing the ViewModel through the hidden control ... why doesn't the ViewModel already know that the results have been refreshed?
If the results are coming from the ViewModel in the first place, and binding is used to display them within your control, then the knowledge that the search results have been refreshed should be driven by your ViewModel - not your view.
In only a few cases have I found a need to break away from ICommands and data-binding.

You should add a dependency property DataRefreshed to your control in order to bind on it
here an example how you can do it
public static readonly DependencyProperty DataRefreshedProperty = DependencyProperty.Register(
"DataRefreshed",
typeof(bool),
typeof("typeof yourcontrol here "),
new FrameworkPropertyMetadata(null,
FrameworkPropertyMetadataOptions.AffectsRender,
new PropertyChangedCallback(OnDataRefreshedChanged)
)
);
public bool DataRefreshed
{
get { return (bool)GetValue(DataRefreshedProperty); }
set { SetValue(DataRefreshedProperty, value); }
}
Then you can manipulate your property like any other WPF property for example SearchResultRefreshed which is defined in your ViewModel
<Control x:Name="SearchResultGrid" ... DataRefreshed="{Binding SearchResultRefreshed}" />
<Button x:Name="SearchResultRefreshedButton" Visibility="Collapsed" Command="{Binding SearchResultRefreshedCommand}" />
take a look at the following tutorial to understand more dependecyproperty and attachedproperty

Related

TreeListControl does not update values of children immediately

I use DevExpress. I have TreeListControl. One column consists Comboboxes (implemented by ComboBoxEditSettings) with several values. When I set a value in Combobox for parent its children will be updated just when I Change Focus or press Enter, but not immidiately.
Here are some Points I have already tried:
Set an Event in ComboBoxEditSettings for an interaction, eg MouseUp;
Set an EventToCommand for eventName EditValueChanged, for example.
Binding is not working;
Set an Event on OkButton_Click, but I have
not found how to get an OkButton_Click Event in Combobox.
Create CustomButton in ComboboxEdit and set a PopUpClose Event. In this
case binding works, but I have not found how to set MyCustomButton
the OkButton Event.
Here is part of my Code. Maybe it helps to understand my case.
<dxg:TreeListControl x:Name="Tree" ItemsSource="{Binding TreeItems, Mode=TwoWay}">
<dxg:TreeListControl.Columns>
<dxg:TreeListColumn FieldName="Node"/>
<dxg:TreeListColumn FieldName="Frequency"/>
<dxg:TreeListColumn.EditSettings>
<dxe:ComboBoxEditSettings ItemsSource="{Binding Samplers}" IsTextEditable="False" DisplayMember="SamplerLongText" ValueMember="SamplerId">
<dxe:ComboBoxEditSettings.StyleSettings>
<dxe:CheckedComboBoxStyleSettings/>
</dxe:ComboBoxEditSettings.StyleSettings>
</dxe:ComboBoxEditSettings>
</dxg:TreeListColumn.EditSettings>
</dxg:TreeListColumn>
</dxg:TreeListControl.Columns>
<dxg:TreeListControl.View>
<dxg:TreeListView
x:Name="TreeListView"
AllowPerPixelScrolling="True"
ShowTotalSummary="True"
KeyFieldName="TreeItemId"
ParentFieldName="ParentId"
ShowCheckboxes="true"
CheckBoxFieldName="IsChecked"
IsCheckBoxEnabledFieldName="IsEnable"
AllowRecursiveNodeChecking="True"
ShowNodeImages="True"
AutoWidth="True"
ImageFieldName="NodeImage"/>
</dxg:TreeListControl.View>
</dxg:TreeListControl>
Thank you in advance!
We have this little helper class for the exact same functionality when using CheckEdits but it should also work for your ComboBoxEdits.
Mind that it utilizes the EditValueChanging-event under the hood which might already answer your question.
public partial class EditorCommitHelper
{
public static readonly DependencyProperty CommitOnValueChangedProperty = DependencyProperty.RegisterAttached("CommitOnValueChanged", typeof(bool), typeof(EditorCommitHelper), new PropertyMetadata(CommitOnValueChangedPropertyChanged));
public static void SetCommitOnValueChanged(GridColumn element, bool value)
{
element.SetValue(CommitOnValueChangedProperty, value);
}
public static bool GetCommitOnValueChanged(GridColumn element)
{
return (bool)element.GetValue(CommitOnValueChangedProperty);
}
private static void CommitOnValueChangedPropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
GridColumn col = source as GridColumn;
if (col.View == null)
Dispatcher.CurrentDispatcher.BeginInvoke(new Action<GridColumn, bool>((column, subscribe) => {
ToggleCellValueChanging(column, subscribe);
}), col, (bool)e.NewValue);
else
ToggleCellValueChanging(col, (bool)e.NewValue);
}
private static void ToggleCellValueChanging(GridColumn col, bool subscribe)
{
TreeListView view = col.View as TreeListView;
if (view == null)
return;
if (subscribe)
view.CellValueChanging += new CellValueChangedEventHandler(view_CellValueChanging);
else
view.CellValueChanging -= view_CellValueChanging;
}
static void view_CellValueChanging(object sender, CellValueChangedEventArgs e)
{
TreeListView view = sender as TreeListView;
if ((bool)e.Column.GetValue(CommitOnValueChangedProperty))
view.PostEditor();
}
}
usage:
<dxg:GridColumn Header="your header"
FieldName="your_field"
your_xmlns:EditorCommitHelper.CommitOnValueChanged="True">
</dxg:GridColumn>

Bind command in view model to keyboard shortcut

I'm using C#, WPF, ReactiveUI and Prism to create an application with many different views (user controls). On some views there are buttons/menu items that bind to a command in the view model. I would like these buttons to also activate using a key combination such as ctrl+s, etc....
What I've tried
InputBindings but that only works when the view that defines these input bindings has focus.
ApplicationCommands the predefined commands like ApplicationCommands.Close seem useful. I can reference them both in the view and the view model, but I don't know how subscribe to them in my view model. It also seems that I have to 'activate' the command first, or at least change CanExecute since any button bound to such command stays disabled.
What I wish for
Let's say I have a view that represents the top menu bar MenuView with a button myButton and a corresponding view model MenuViewModel with a command myCommand. I would like to bind myButton to myCommand and the keyboard shortcut ctrl+u to myCommand without MenuView knowing about the implementation of its view model. The keyboard shortcut should work as long as the window that contains MenuView has focus.
I don't really care if the keyboard short-cut is either in the view or view model.
You could create an attached Blend behaviour that handles the PreviewKeyDown event of the parent window:
public class KeyboardShortcutBehavior : Behavior<FrameworkElement>
{
private Window _parentWindow;
public static readonly DependencyProperty CommandProperty =
DependencyProperty.Register(nameof(Command), typeof(ICommand),
typeof(KeyboardShortcutBehavior), new FrameworkPropertyMetadata(null));
public ICommand Command
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
public static readonly DependencyProperty ModifierKeyProperty =
DependencyProperty.Register(nameof(ModifierKey), typeof(ModifierKeys),
typeof(KeyboardShortcutBehavior), new FrameworkPropertyMetadata(ModifierKeys.None));
public ModifierKeys ModifierKey
{
get { return (ModifierKeys)GetValue(ModifierKeyProperty); }
set { SetValue(ModifierKeyProperty, value); }
}
public static readonly DependencyProperty KeyProperty =
DependencyProperty.Register(nameof(Key), typeof(Key),
typeof(KeyboardShortcutBehavior), new FrameworkPropertyMetadata(Key.None));
public Key Key
{
get { return (Key)GetValue(KeyProperty); }
set { SetValue(KeyProperty, value); }
}
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.Loaded += AssociatedObject_Loaded;
AssociatedObject.Unloaded += AssociatedObject_Unloaded;
}
private void AssociatedObject_Loaded(object sender, RoutedEventArgs e)
{
_parentWindow = Window.GetWindow(AssociatedObject);
if(_parentWindow != null)
{
_parentWindow.PreviewKeyDown += ParentWindow_PreviewKeyDown;
}
}
private void ParentWindow_PreviewKeyDown(object sender, KeyEventArgs e)
{
if(Command != null && ModifierKey != ModifierKeys.None && Key != Key.None && Keyboard.Modifiers == ModifierKey && e.Key == Key)
Command.Execute(null);
}
private void AssociatedObject_Unloaded(object sender, RoutedEventArgs e)
{
if(_parentWindow != null)
{
_parentWindow.PreviewKeyDown -= ParentWindow_PreviewKeyDown;
}
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.Loaded -= AssociatedObject_Loaded;
AssociatedObject.Unloaded -= AssociatedObject_Loaded;
}
}
Sample usage:
<TextBox xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity">
<i:Interaction.Behaviors>
<local:KeyboardShortcutBehavior ModifierKey="Ctrl" Key="U" Command="{Binding myCommand}" />
</i:Interaction.Behaviors>
</TextBox>
In code behind easy. Create some utility function that eventually lead to an observable of the parent window key events. Note that you will need the ReactiveUI.Events library.
Some utils for handling load and unload of controls.
public static void LoadUnloadHandler
( this FrameworkElement control
, Func<IDisposable> action
)
{
var state = false;
var cleanup = new SerialDisposable();
Observable.Merge
(Observable.Return(control.IsLoaded)
, control.Events().Loaded.Select(x => true)
, control.Events().Unloaded.Select(x => false)
)
.Subscribe(isLoadEvent =>
{
if (!state)
{
// unloaded state
if (isLoadEvent)
{
state = true;
cleanup.Disposable = new CompositeDisposable(action());
}
}
else
{
// loaded state
if (!isLoadEvent)
{
state = false;
cleanup.Disposable = Disposable.Empty;
}
}
});
}
public static IObservable<T> LoadUnloadHandler<T>(this FrameworkElement control, Func<IObservable<T>> generator)
{
Subject<T> subject = new Subject<T>();
control.LoadUnloadHandler(() => generator().Subscribe(v => subject.OnNext(v)));
return subject;
}
and one specifically for handling the window of a loaded control
public static IObservable<T> LoadUnloadHandler<T>
(this FrameworkElement control, Func<Window, IObservable<T>> generator)
{
Subject<T> subject = new Subject<T>();
control.LoadUnloadHandler(() => generator(Window.GetWindow(control)).Subscribe(v => subject.OnNext(v)));
return subject;
}
and finally a key handler for the parent window of any control
public static IObservable<KeyEventArgs> ParentWindowKeyEventObservable(this FrameworkElement control)
=> control.LoadUnloadHandler((Window window) => window.Events().PreviewKeyDown);
now you can do
Button b;
b.ParentWindowKeyEventObservable()
.Subscribe( kEvent => {
myCommand.Execute();
}
It might seem a bit complex but I use the LoadUnloadHandler on most user controls to aquire and dispose resources as the UI lifecycle progresses.
You want to use KeyBindings for this. This allows you to bind keyboard key combos to a command. Read the docs here: https://msdn.microsoft.com/en-us/library/system.windows.input.keybinding(v=vs.110).aspx

Fire event when ItemsSource of ListView changes

I can't find an event that can be called after a window was loaded, and where i can get access to the ItemsSource of a ListView. The only thing i can think is the Loaded event in the ListView but when this event is fired the ItemsSource remains null.
I probably need another event so i can know what is in the ItemsSource.
So with code i will probably expose better what i am trying to do:
In a custom class:
public class GridViewSomething
{
public static readonly DependencyProperty TestProperty =
DependencyProperty.RegisterAttached("Test",
typeof(bool),
typeof(GridViewSomething),
new UIPropertyMetadata(OnTestChanged));
public static bool GetTest(DependencyObject obj)
{
return (bool)obj.GetValue(TestProperty);
}
public static void SetTest(DependencyObject obj, bool value)
{
obj.SetValue(TestProperty, value);
}
static void OnTestChanged(object sender, DependencyPropertyChangedEventArgs e)
{
ListView listView = sender as ListView;
if (!(bool)e.OldValue && (bool)e.NewValue)
listView.AddHandler(ListView.LoadedEvent, new RoutedEventHandler(ListView_Loaded));
else if (!(bool)e.NewValue && (bool)e.OldValue)
listView.RemoveHandler(ListView.LoadedEvent, new RoutedEventHandler(ListView_Loaded);
}
static void ListView_Loaded(object sender, RoutedEventArgs e)
{
ListView listView = sender as ListView;
if (listView.ItemsSource != null)
{
//Do some work
}
}
}
And the ListView:
(...)
<ListView ItemsSource="{Binding Students}"
test:GridViewSomething.Test="True">
(...)
I am binding the ListView to a Collection in the ViewModel of this Window. I need to know precisely what is in the ItemsSource in that custom class.
So how i can achieve this?
Thanks in advance!
You could subsribe to changes on the ItemsSource property by using a descriptor as shown here. If check the value in that handler it should not be null (unless the bound property was in fact set to null).

Prism + MVVM + Access Keys + UpdateSourceTrigger="LostFocus" -- This doesn't let me save an updated textbox without first losing focus

Not really sure how to tackle this issue:
I have a "Save" button that has access keys attached to it... but, if I type something into a textbox and press the access keys to save, the textbox doesn't update my viewmodel because it never lost focus. Any way to solve this outside of changing the UpdateSourceTrigger to PropertyChanged?
Your problem is the UpdateSourceTrigger="LostFocus"
This is default for TextBoxes, and means that the TextBox will only update its bound value when it loses focus
One way to force it to update without setting UpdateSourceTrigger="PropertyChanged" is to hook into the KeyPress event and if the key combination is something that would trigger a save, call UpdateSource() first
Here's an Attached Property I like using when the Enter key should update the source.
It is used like this:
<TextBox Text="{Binding Name}"
local:TextBoxProperties.EnterUpdatesTextSource="True" />
and the Attached Property definition looks like this:
public class TextBoxProperties
{
public static readonly DependencyProperty EnterUpdatesTextSourceProperty =
DependencyProperty.RegisterAttached("EnterUpdatesTextSource", typeof(bool), typeof(TextBoxProperties),
new PropertyMetadata(false, EnterUpdatesTextSourcePropertyChanged));
// Get
public static bool GetEnterUpdatesTextSource(DependencyObject obj)
{
return (bool)obj.GetValue(EnterUpdatesTextSourceProperty);
}
// Set
public static void SetEnterUpdatesTextSource(DependencyObject obj, bool value)
{
obj.SetValue(EnterUpdatesTextSourceProperty, value);
}
// Changed Event - Attach PreviewKeyDown handler
private static void EnterUpdatesTextSourcePropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var sender = obj as UIElement;
if (obj != null)
{
if ((bool)e.NewValue)
{
sender.PreviewKeyDown += OnPreviewKeyDown_UpdateSourceIfEnter;
}
else
{
sender.PreviewKeyDown -= OnPreviewKeyDown_UpdateSourceIfEnter;
}
}
}
// If key being pressed is the Enter key, and EnterUpdatesTextSource is set to true, then update source for Text property
private static void OnPreviewKeyDown_UpdateSourceIfEnter(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter)
{
if (GetEnterUpdatesTextSource((DependencyObject)sender))
{
var obj = sender as UIElement;
BindingExpression textBinding = BindingOperations.GetBindingExpression(
obj, TextBox.TextProperty);
if (textBinding != null)
textBinding.UpdateSource();
}
}
}
}

Get the value of an attached property rather than set it

I have some code to set the focused property of a text box, but what i'm actually after is finding out if the text box currently has the keyboard focus, I need to determine this from my view model
public static class FocusExtension
{
public static bool GetIsFocused(DependencyObject obj)
{
return (bool)obj.GetValue(IsFocusedProperty);
}
public static void SetIsFocused(DependencyObject obj, bool value)
{
obj.SetValue(IsFocusedProperty, value);
}
public static readonly DependencyProperty IsFocusedProperty = DependencyProperty.RegisterAttached
(
"IsFocused",
typeof(bool),
typeof(FocusExtension),
new UIPropertyMetadata(false, OnIsFocusedPropertyChanged)
);
public static void OnIsFocusedPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var uie = (UIElement)d;
if ((bool)e.NewValue)
{
uie.Focus();
}
}
}
And the xaml is
<TextBox Text="{Binding Path=ClientCode}" c:FocusExtension.IsFocused="{Binding IsClientCodeFocused}" />
source of code
have you seen the FocusManager? you can get/set focus using this object.
Edit
Based on the comments below, here's an example of an attached property that hooks up an event and updates the source of a binding. I'll add comments where I know you'll need to make modifications. Hopefully it will point you in the right direction
public class TextBoxHelper
{
// I excluded the generic stuff, but the property is called
// EnterUpdatesSource and it makes a TextBox update it's source
// whenever the Enter key is pressed
// Property Changed Event - You have this in your class above
private static void EnterUpdatesTextSourcePropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
UIElement sender = obj as UIElement;
if (obj != null)
{
// In my case, the True/False value just determined a behavior,
// so toggling true/false added/removed an event.
// Since you want your events to be on at all times, you'll either
// want to have two AttachedProperties (one to tell the control
// that it should be tracking the current focused state, and
// another for binding the actual focused state), or you'll want
// to find a way to only add the EventHandler when the
// AttachedProperty is first added and not toggle it on/off as focus
// changes or add it repeatedly whenever this value is set to true
// You can use the GotFocus and LostFocus Events
if ((bool)e.NewValue == true)
{
sender.PreviewKeyDown += new KeyEventHandler(OnPreviewKeyDownUpdateSourceIfEnter);
}
else
{
sender.PreviewKeyDown -= OnPreviewKeyDownUpdateSourceIfEnter;
}
}
}
// This is the EventHandler
static void OnPreviewKeyDownUpdateSourceIfEnter(object sender, KeyEventArgs e)
{
// You won't need this
if (e.Key == Key.Enter)
{
// or this
if (GetEnterUpdatesTextSource((DependencyObject)sender))
{
// But you'll want to take this bit and modify it so it actually
// provides a value to the Source based on UIElement.IsFocused
UIElement obj = sender as UIElement;
// If you go with two AttachedProperties, this binding should
// point to the property that contains the IsFocused value
BindingExpression textBinding = BindingOperations.GetBindingExpression(
obj, TextBox.TextProperty);
// I know you can specify a value for a binding source, but
// I can't remember the exact syntax for it right now
if (textBinding != null)
textBinding.UpdateSource();
}
}
}
There might be a better way of accomplishing what you're trying to do, but if not then I hope this provides a good starting point :)
in your OnIsFocusedPropertyChanged handler, you need to get a reference to the control that it is being set on and subscribe to its FocusChanged event, where you can re-set the dependency pproperty. Make sure in your XAML you set the binding mode to TwoWay

Categories