How to make a general wpf mvvm window open/close eventhandler? - wpf

This is my current App.xaml.cs
Its looks simple for one or two, but I have 7-8 windows.
Is there a clever way to make this a little more general and better?
public App()
{
_ViewModel = new MyAppViewModel();
_ViewModel.OpenXXXWindowEvent += new EventHandler(ViewModel_OpenXXXWindow);
_ViewModel.OpenYYYWindowEvent += new EventHandler(ViewModel_OpenYYYWindow);
...
}
private void ViewModel_OpenXXXWindow(object sender, EventArgs e)
{
_XXXWindow = new XXXWindow();
_XXXWindow.DataContext = _ViewModel;
_XXXWindow.ShowDialog();
}
private void ViewModel_CloseXXXWindow(object sender, EventArgs e)
{
if (_XXXWindow != null)
_XXXWindow.Close();
}
private void ViewModel_OpenYYYWindow(object sender, EventArgs e)
{
_YYYWindow = new YYYWindow();
_YYYWindow.DataContext = _ViewModel;
_YYYWindow.ShowDialog();
}
private void ViewModel_CloseYYYWindow(object sender, EventArgs e)
{
if (_YYYWindow != null)
_YYYWindow.Close();
}
...

Too much XAML code-behind is a signal that you're somehow breaking the MVVM pattern. A ViewModel receiving EventArgs is a no-no too.
To open/close dialogs I tend to use a messaging system, for example, the one provided by MVVM Light.
With a messaging system (using MVVM Light) you do something like this:
In your ViewModel:
private void SomeMethodThatNeedsToOpenADialog()
{
Messenger.Default.Send(new OpenDialogXMessage());
}
And in your View:
Messenger.Default.Register<OpenDialogXMessage>(this, (msg) => {
new DialogX().ShowDialog();
});
Some relevant links:
How to open a new window using MVVM Light Toolkit
Show dialog with MVVM Light toolkit

Well it's not a event handler solution, but how about a Binding solution? Unfortunately, you cannot bind Window.DialogResult, which causes the window to close, when the value is set. But you could create an AttachedProperty which can be bound to a property on the underlying ViewModel and sets the not bindable property, when its value is set. The AttachedProperty looks like this.
public class AttachedProperties
{
public static readonly DependencyProperty DialogResultProperty =
DependencyProperty.RegisterAttached("DialogResult", typeof (bool?), typeof (AttachedProperties), new PropertyMetadata(default(bool?), OnDialogResultChanged));
private static void OnDialogResultChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var wnd = d as Window;
if (wnd == null)
return;
wnd.DialogResult = (bool?) e.NewValue; //here the not bindable property is set and the windows is closed
}
public static bool? GetDialogResult(DependencyObject dp)
{
if (dp == null) throw new ArgumentNullException("dp");
return (bool?)dp.GetValue(DialogResultProperty);
}
public static void SetDialogResult(DependencyObject dp, object value)
{
if (dp == null) throw new ArgumentNullException("dp");
dp.SetValue(DialogResultProperty, value);
}
}
The AttachedProperty can be used like this
<Window x:Class="AC.Frontend.Controls.DialogControl.Dialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:hlp="clr-namespace:AC.Frontend.Helper"
hlp:AttachedProperties.DialogResult="{Binding DialogResult}">
<!-- put your content here -->
</Window>
Now you can use a Command to set the DialogResult property of the VM, which is the DataContext of the Window.

Related

How to communicate from view model to view in MVVM in WPF

I want to execute certain code in view if something happens in view model . I have looked into Prism event aggregator but I havent got success with prism 5. If there is more easier method to do so it will be helpful.Any blog or same code regarding this will also work
As Ed Plunkett says, the thing to do is listen for DataContextChanged in your view, as this is how View's are connected to ViewModels.
Here's an example:
public partial class MyView : UserControl
{
public MyView ()
{
DataContextChanged += MyView_DataContextChanged;
}
private void MyView_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
//new ViewModel has been set.
MyViewModel myViewModel = e.NewValue as MyViewModel;
if (myViewModel != null)
{
//check for property changes
myViewModel.PropertyChanged += MyViewModel_PropertyChanged;
//custom event for specific update
myViewModel.MyCustomEventTriggered += MyViewModel_MyCustomEventTriggered
}
}
private void MyViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
//do your logic
}
private void MyViewModel_MyCustomEventTriggered(object sender, MyCustomEventArgs e)
{
//do your logic
}
}

Setting Focus on TextBox from ViewModel in WPF

I am having a master window in which there are plenty of user control. and using navigation i am able to access the user controls. But by question is how to set focus on the first text box when ever the user control is opened.
I tried with dependency property and boolean flags, i was able to succeeded a bit. When i first render the UserControl i was able to focus but when i open for the second time i was not able to set focus on the TextBox.
And one more thing, i have validation for TextBoxes, if the validation fails then the textbox should be emptied and the focus should be on the respective text box.
How can i achieve this using MVVM in WPF (CLR 3.5, VS2008)
thanks in advance.
If you have a UserControl then you also have CodeBehind.
Place this inside your codebehind and you will do fine.
this.Loaded += (o, e) => { Keyboard.Focus(textBox1) }
Place this inside your UserControl XAML if you wish to listen to validation errors.
<UserControl>
<Grid Validation.Error="OnValidationError">
<TextBox Text{Binding ..., NotifyOnValidationError=true } />
</Grid>
<UserControl>
Inside the CodeBehind of your UserControl you will have something like this:
public void OnValidationError(o , args)
{
if(o is TextBox)
{
(TextBox)o).Text = string.Empty;
}
}
You should use AttachedProperty to stick to MVVM pattern it'll keep your view model independent of UI code and fully unit testable. Following attached property binds a boolean property to focus and highlight the TextBox, if you do not want the highlighting then you can remove the highlighting code and just work with focus code.
public class TextBoxBehaviors
{
#region HighlightTextOnFocus Property
public static readonly DependencyProperty HighlightTextOnFocusProperty =
DependencyProperty.RegisterAttached("HighlightTextOnFocus", typeof (bool), typeof (TextBoxBehaviors),
new PropertyMetadata(false, HighlightTextOnFocusPropertyChanged));
public static bool GetHighlightTextOnFocus(DependencyObject obj)
{
return (bool) obj.GetValue(HighlightTextOnFocusProperty);
}
public static void SetHighlightTextOnFocus(DependencyObject obj, bool value)
{
obj.SetValue(HighlightTextOnFocusProperty, value);
}
private static void HighlightTextOnFocusPropertyChanged(DependencyObject sender,
DependencyPropertyChangedEventArgs e)
{
var uie = sender as UIElement;
if (uie == null) return;
if ((bool) e.NewValue)
{
uie.GotKeyboardFocus += OnKeyboardFocusSelectText;
uie.PreviewMouseLeftButtonDown += OnMouseLeftButtonDownSetFocus;
}
else
{
uie.GotKeyboardFocus -= OnKeyboardFocusSelectText;
uie.PreviewMouseLeftButtonDown -= OnMouseLeftButtonDownSetFocus;
}
}
private static void OnKeyboardFocusSelectText(object sender, KeyboardFocusChangedEventArgs e)
{
var textBox = sender as TextBox;
if (textBox == null) return;
textBox.SelectAll();
}
private static void OnMouseLeftButtonDownSetFocus(object sender, MouseButtonEventArgs e)
{
var textBox = sender as TextBox;
if (textBox == null) return;
if (!textBox.IsKeyboardFocusWithin)
{
textBox.Focus();
e.Handled = true;
}
}
#endregion
}
You can use this attached property in on your TextBox which you want to focus/highlight...
<TextBox ... local:TextBoxBehaviors.HighlightTextOnFocus="{Binding IsScrolledToEnd}" ... />
You can also try using FocusManager
<UserControl>
<Grid FocusManager.FocusedElement="{Binding Path=FocusedTextBox, ElementName=UserControlName}">
<TextBox x:Name="FocusedTextBox" />
</Grid>
<UserControl>

Initial Focus and Select All behavior

I have a user control that is nested inside a window that is acting as a shell for a dialog display. I ignore focus in the shell window, and in the hosted user control I use the FocusManager to set the initial focus to a named element (a textbox) as shown below.
This works, setting the cursor at the beginning of the named textbox; however I want all text to be selected.
The TextBoxSelectionBehavior class (below) usually does exactly that, but not in this case. Is there an easy xaml fix to get the text in the named textbox selected on initial focus?
Cheers,
Berryl
TextBox Selection Behavior
// in app startup
TextBoxSelectionBehavior.RegisterTextboxSelectionBehavior();
/// <summary>
/// Helper to select all text in the text box on entry
/// </summary>
public static class TextBoxSelectionBehavior
{
public static void RegisterTextboxSelectionBehavior()
{
EventManager.RegisterClassHandler(typeof(TextBox), UIElement.GotFocusEvent, new RoutedEventHandler(OnTextBox_GotFocus));
}
private static void OnTextBox_GotFocus(object sender, RoutedEventArgs e)
{
var tb = (sender as TextBox);
if (tb != null)
tb.SelectAll();
}
}
The hosted UserControl
<UserControl
<DockPanel KeyboardNavigation.TabNavigation="Local"
FocusManager.FocusedElement="{Binding ElementName=tbLastName}" >
<TextBox x:Name="tbLastName" ... />
stop gap solution
Per comments with Rachel below, I ditched the FocusManger in favor of some code behind:
tbLastName.Loaded += (sender, e) => tbLastName.Focus();
Still would love a declarative approach for a simple and common chore though...
I usually use an AttachedProperty to make TextBoxes highlight their text on focus. It is used like
<TextBox local:HighlightTextOnFocus="True" />
Code for attached property
public static readonly DependencyProperty HighlightTextOnFocusProperty =
DependencyProperty.RegisterAttached("HighlightTextOnFocus",
typeof(bool), typeof(TextBoxProperties),
new PropertyMetadata(false, HighlightTextOnFocusPropertyChanged));
[AttachedPropertyBrowsableForChildrenAttribute(IncludeDescendants = false)]
[AttachedPropertyBrowsableForType(typeof(TextBox))]
public static bool GetHighlightTextOnFocus(DependencyObject obj)
{
return (bool)obj.GetValue(HighlightTextOnFocusProperty);
}
public static void SetHighlightTextOnFocus(DependencyObject obj, bool value)
{
obj.SetValue(HighlightTextOnFocusProperty, value);
}
private static void HighlightTextOnFocusPropertyChanged(
DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var sender = obj as UIElement;
if (sender != null)
{
if ((bool)e.NewValue)
{
sender.GotKeyboardFocus += OnKeyboardFocusSelectText;
sender.PreviewMouseLeftButtonDown += OnMouseLeftButtonDownSetFocus;
}
else
{
sender.GotKeyboardFocus -= OnKeyboardFocusSelectText;
sender.PreviewMouseLeftButtonDown -= OnMouseLeftButtonDownSetFocus;
}
}
}
private static void OnKeyboardFocusSelectText(
object sender, KeyboardFocusChangedEventArgs e)
{
var textBox = e.OriginalSource as TextBox;
if (textBox != null)
{
textBox.SelectAll();
}
}
private static void OnMouseLeftButtonDownSetFocus(
object sender, MouseButtonEventArgs e)
{
TextBox tb = FindAncestor<TextBox>((DependencyObject)e.OriginalSource);
if (tb == null)
return;
if (!tb.IsKeyboardFocusWithin)
{
tb.Focus();
e.Handled = true;
}
}
static T FindAncestor<T>(DependencyObject current)
where T : DependencyObject
{
current = VisualTreeHelper.GetParent(current);
while (current != null)
{
if (current is T)
{
return (T)current;
}
current = VisualTreeHelper.GetParent(current);
};
return null;
}
Edit
Based on comments below, what about just getting rid of the FocusManager.FocusedElement and setting tb.Focus() and tb.SelectAll() in the Loaded event of your TextBox?
As stated above, you can add an event handler for the Loaded event to set focus and select all text:
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
base.DataContext = new Person { FirstName = "Joe", LastName = "Smith" };
base.Loaded += delegate
{
this._firstNameTextBox.Focus();
this._firstNameTextBox.SelectAll();
};
}
}

A good aproach to events in MVVM

So I ran into this problem trying to implement MVVM. AFAIK the best way to execute a method in the ViewModel class is through a CommandBinding.
<Button Command={Binding DoSomethingCommand} />
Only this time I need to do something on a ListBoxItem double click, and the ListBoxItem doesn't implement ICommandSource. So I'm wondering what's the best approach to do this, if there is one.
Thanks!
Edit:
I just thought of a way, but it seems rather hacky. What if I expose the ListBox.DoubleClick event, and my ViewModel class subscribes to it and runs the correct method when the DoubleClick is fired?
You could handle the event in the code-behind file and call the method on the ViewModel object. In my opinion this is a lot better than starting to hack. :-) I won’t pass a WPF routed event to a ViewModel object.
Who says that code-behind is forbidden? The Model-View-ViewModel pattern definitely not.
You can used attached behaviors.
See here: Link
Silverlight doesn't contain a Command button like the Button in WPF. The way we get around it there is to create a custom control that contains a command and maps that event to the command. Something like this should work.
public class CommandListBoxItem : ListBoxItem
{
public CommandListBoxItem()
{
DoubleClick += (sender, e) =>
{
if (Command != null && Command.CanExecute(CommandParameter))
Command.Execute(CommandParameter);
};
}
#region Bindable Command Properties
public static DependencyProperty DoubleClickCommandProperty =
DependencyProperty.Register("DoubleClickCommand",
typeof(ICommand), typeof(CommandListBoxItem),
new PropertyMetadata(null, DoubleClickCommandChanged));
private static void DoubleClickCommandChanged(DependencyObject source, DependencyPropertyChangedEventArgs args)
{
var item = source as CommandListBoxItem;
if (item == null) return;
item.RegisterCommand(args.OldValue as ICommand, args.NewValue as ICommand);
}
public ICommand DoubleClickCommand
{
get { return GetValue(DoubleClickCommandProperty) as ICommand; }
set { SetValue(DoubleClickCommandProperty, value); }
}
public static DependencyProperty DoubleClickCommandParameterProperty =
DependencyProperty.Register("DoubleClickCommandParameter",
typeof(object), typeof(CommandListBoxItem),
new PropertyMetadata(null));
public object DoubleClickCommandParameter
{
get { return GetValue(DoubleClickCommandParameterProperty); }
set { SetValue(DoubleClickCommandParameterProperty, value); }
}
#endregion
private void RegisterCommand(ICommand oldCommand, ICommand newCommand)
{
if (oldCommand != null)
oldCommand.CanExecuteChanged -= HandleCanExecuteChanged;
if (newCommand != null)
newCommand.CanExecuteChanged += HandleCanExecuteChanged;
HandleCanExecuteChanged(newCommand, EventArgs.Empty);
}
private void HandleCanExecuteChanged(object sender, EventArgs args)
{
if (DoubleClickCommand != null)
IsEnabled = DoubleClickCommand.CanExecute(DoubleClickCommandParameter);
}
}
Then when you create your ListBoxItems you bind to the new Command Property.
<local:CommandListBoxItem DoubleClickCommand="{Binding ItemDoubleClickedCommand}" />

Workaround for UpdateSourceTrigger LostFocus on Silverlight Datagrid?

I have a Silverlight 2 application that validates data OnTabSelectionChanged. Immediately I began wishing that UpdateSourceTrigger allowed more than just LostFocus because if you click the tab without tabbing off of a control the LINQ object is not updated before validation.
I worked around the issue for TextBoxes by setting focus to another control and then back OnTextChanged:
Private Sub OnTextChanged(ByVal sender As Object, ByVal e As TextChangedEventArgs)
txtSetFocus.Focus()
sender.Focus()
End Sub
Now I am trying to accomplish the same sort of hack within a DataGrid. My DataGrid uses DataTemplates generated at runtime for the CellTemplate and CellEditingTemplate. I tried writing the TextChanged="OnTextChanged" into the TextBox in the DataTemplate, but it is not triggered.
Anyone have any ideas?
You can do it with a behavior applied to the textbox too
// xmlns:int is System.Windows.Interactivity from System.Windows.Interactivity.DLL)
// xmlns:behavior is your namespace for the class below
<TextBox Text="{Binding Description,Mode=TwoWay,UpdateSourceTrigger=Explicit}">
<int:Interaction.Behaviors>
<behavior:TextBoxUpdatesTextBindingOnPropertyChanged />
</int:Interaction.Behaviors>
</TextBox>
public class TextBoxUpdatesTextBindingOnPropertyChanged : Behavior<TextBox>
{
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.TextChanged += new TextChangedEventHandler(TextBox_TextChanged);
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.TextChanged -= TextBox_TextChanged;
}
void TextBox_TextChanged(object sender, TextChangedEventArgs e)
{
var bindingExpression = AssociatedObject.GetBindingExpression(TextBox.TextProperty);
bindingExpression.UpdateSource();
}
}
This blog post shows how to update the source of a textbox explicitly using attached property:
http://www.thomasclaudiushuber.com/blog/2009/07/17/here-it-is-the-updatesourcetrigger-for-propertychanged-in-silverlight/
You could easily modify it to work with other controls as well...
I ran into this same problem using MVVM and Silverlight 4. The problem is that the binding does not update the source until after the textbox looses focus, but setting focus on another control doesn't do the trick.
I found a solution using a combination of two different blog posts. I used the code from Patrick Cauldwell's DefaultButtonHub concept, with one "SmallWorkaround" from SmallWorkarounds.net
http://www.cauldwell.net/patrick/blog/DefaultButtonSemanticsInSilverlightRevisited.aspx
www.smallworkarounds.net/2010/02/elementbindingbinding-modes.html
My change resulted in the following code for the DefaultButtonHub class:
public class DefaultButtonHub
{
ButtonAutomationPeer peer = null;
private void Attach(DependencyObject source)
{
if (source is Button)
{
peer = new ButtonAutomationPeer(source as Button);
}
else if (source is TextBox)
{
TextBox tb = source as TextBox;
tb.KeyUp += OnKeyUp;
}
else if (source is PasswordBox)
{
PasswordBox pb = source as PasswordBox;
pb.KeyUp += OnKeyUp;
}
}
private void OnKeyUp(object sender, KeyEventArgs arg)
{
if (arg.Key == Key.Enter)
if (peer != null)
{
if (sender is TextBox)
{
TextBox t = (TextBox)sender;
BindingExpression expression = t.GetBindingExpression(TextBox.TextProperty);
expression.UpdateSource();
}
((IInvokeProvider)peer).Invoke();
}
}
public static DefaultButtonHub GetDefaultHub(DependencyObject obj)
{
return (DefaultButtonHub)obj.GetValue(DefaultHubProperty);
}
public static void SetDefaultHub(DependencyObject obj, DefaultButtonHub value)
{
obj.SetValue(DefaultHubProperty, value);
}
// Using a DependencyProperty as the backing store for DefaultHub. This enables animation, styling, binding, etc...
public static readonly DependencyProperty DefaultHubProperty =
DependencyProperty.RegisterAttached("DefaultHub", typeof(DefaultButtonHub), typeof(DefaultButtonHub), new PropertyMetadata(OnHubAttach));
private static void OnHubAttach(DependencyObject source, DependencyPropertyChangedEventArgs prop)
{
DefaultButtonHub hub = prop.NewValue as DefaultButtonHub;
hub.Attach(source);
}
}
This should be included in some sort of documentation for Silverlight :)
I know it's old news... but I got around this by doing this:
Text="{Binding Path=newQuantity, UpdateSourceTrigger=PropertyChanged}"

Resources