I've a Custom Textbox
control which is working just fine.
public class TextBoxExt : TextBox
{
public event ehEmptyArgument ClearAll, Save;
public TextBoxExt()
{
SetResourceReference(StyleProperty, typeof(TextBoxExt));
SetValue(FocusNextControlProperty, true);
}
#region FocusNextControl
public static DependencyProperty FocusNextControlProperty =
DependencyProperty.Register("FocusNextControl", typeof(bool), typeof(TextBoxExt),
new PropertyMetadata(true, new PropertyChangedCallback(OnFocusNextControl)));
public bool FocusNextControl
{
get => Convert.ToBoolean(GetValue(FocusNextControlProperty));
set => SetValue(FocusNextControlProperty, value);
}
static void OnFocusNextControl(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
TextBoxExt dt = d as TextBoxExt;
dt.FocusNextControl = Convert.ToBoolean(e.NewValue);
}
#endregion
protected override void OnKeyUp(KeyEventArgs e)
{
if (e.Key == Key.Enter)
{
if (FocusNextControl) UI.ProcessTabKey(true);
else Save?.Invoke();
}
else if (e.Key == Key.Escape) ClearAll?.Invoke();
base.OnKeyUp(e);
}
}
Here as you can see Save & ClearAll are events which are invoked on Key Pressed
and are handled in c# code for XAML
Form.
Now I'm completely shifting towards ViewModel
and I want to avoid writing code in XAML C#
file.
How can I achieve this ?
Additional Information for Clear Understanding based on Quires in Comment
I've also created custom controls such as ComboboxExt
, DateTextBox
, NumericTextBox
, etc. with same functionality
They all are used in data entry form so that users do not need to use mouse or tab keys to click button
Save event is triggered on last control of entire form to let code behind know that user has completed entry of all the data and validation and updating is handled in ViewModel
Clear event is triggered when user presses escape key
to cancel any type of modification done by user and also to close the form. This is also handled in ViewModel
Here they have been helpful in fast data entry without touching mouse again and again.
It's very easy to make custom WPF controls MVVM friendly. All you have to do is create ICommand
-typed DependencyProperty
s and - instead of invoking event
s - call ICommand.Execute
instead:
#region ICommand SaveCommand dependency property
public static DependencyProperty SaveCommandProperty = DependencyProperty.Register(
"SaveCommand",
typeof(ICommand),
typeof(TextBoxExt),
new PropertyMetadata((ICommand)null));
public ICommand SaveCommand
{
get
{
return (ICommand)GetValue(SaveCommandProperty);
}
set
{
SetValue(SaveCommandProperty, value);
}
}
#endregion
#region ICommand ClearAllCommand dependency property
public static DependencyProperty ClearAllCommandProperty = DependencyProperty.Register(
"ClearAllCommand",
typeof(ICommand),
typeof(TextBoxExt),
new PropertyMetadata((ICommand)null));
public ICommand ClearAllCommand
{
get
{
return (ICommand)GetValue(ClearAllCommandProperty);
}
set
{
SetValue(ClearAllCommandProperty, value);
}
}
#endregion
protected override void OnKeyUp(KeyEventArgs e)
{
if (e.Key == Key.Enter)
{
if (FocusNextControl)
UI.ProcessTabKey(true);
else
this.SaveCommand?.Execute(null);
}
else if (e.Key == Key.Escape)
this.ClearAllCommand?.Execute(null);
base.OnKeyUp(e);
}
In XAML you can then bind to these properties exactly the same way you might bind to Button.Command
, etc. If you need to pass more information along to the command, you can always define application-specific argument types in a shared assembly and pass them to your view models as the argument to Execute
.
While this works great for your own custom controls, know that you can also employ a similar technique with any control by using an attached command pattern, as I describe here: Assigns a function to PreviewTextInput in the Texbox preserving the WPF pattern.
I also want to address whether these kinds of events are suitable to be handled via bound commands in a view model. The truth is there's no hard and fast rule dictating whether abstract logic is sufficiently "UI-related" that it belongs in the platform-specific view or the platform-agnostic view model. Often the conceptual line is blurry and those who purport to give you advice here have no workable standard to offer other than "I say so".
The only objective, black and white standard I recommend you follow is this: if the logic can be expressed with relative ease and elegance in a view model independent of any WPF (or other platform)-specific types or code, then that's where it belongs. Conversely if you have UI-specific logic that transcends any one application and/or depends heavily on the specifics of the platform (i.e. WPF), then it is probably best implemented by the view. I have more to say about all this here if interested: In MVVM is it acceptable to access the ViewModel in the view's code behind?.
That said, with clever tricks like interfaces and dependency injection, it is possible to handle virtually all UI logic in a platform-independent, yet overly complex way and thus take this advice too far. (I am guilty of doing this when I first learned of the MVVM pattern 10 years ago). The question to ask is, are you fighting the current and twisting yourself into logical knots just to try to keep code out of the view? Are you spending far more time scaffolding bespoke interfaces and attached commands than you would be with a more straightforward platform-specific approach? If so then you may want to rethink things.
But I see no danger of that here. The view model-based solution is straightforward and elegant, and the modifications to your custom TextBox
simple. As you're using a custom control (which is great, by the way) you've now got an MVVM-friendly UI component that can be reused across all your WPF applications because it's untethered to your application specifics. And should you ever want to port your app to another platform, because you stayed away from messy single-purpose .xaml.cs code-behind, you'll have that much less platform-specific code to replace. This is exactly the right way to practice MVVM and maximize the value of every line of code - which, at the end of the day, is the only reason to follow any design pattern.
I expect this answer will receive at least a couple of downvotes because, for some reason, the pragmatic approach I advocate is not popular on this site. However I suggest you ignore that and simply try this solution, and ultimately use your own judgment about the best way to architect your application. In my opinion, at least, your instincts and the path you appear to be on are both 100% correct.