I have a Floating-window template in which i load a Message-box by initializing the MessageBoxViewModel object to display the message
I want to close this pop up when user clicks on the Close button. How should i do this.
I have written the Close button command in the MessageBoxViewModel .
public class MessageBoxViewModel : ViewModelBase
{
public MessageBoxViewModel ( string messageText)
{
// load all the fields
}
}
private string message;
public string Message
{
get
{
return message;
}
set
{
if (value == message)
return;
message = value;
base.OnPropertyChanged("Message");
}
}
#region Commands
RelayCommand okay;
public ICommand OKAY
{
get
{
if (okay == null)
{
okay = new RelayCommand(
param => this.CallOkay()
);
}
return okay;
}
}
#endregion
void CallOkay()
{
// should write logic to close this window
}
The approach another MVVM framework uses (Caliburn Micro) is essentially just using events from the VM.
However, to extend the idea into a reusable 'module' Caliburn Micro uses a Conductor class which manages the relationship between the lifecycle of the View and the lifecycle of the ViewModel. An interface on the ViewModel which marks it as 'closable' is required, and you do need to write a conductor specific to the window/dialog implementation you are using (assuming it doesn't subclass from standard Window).
Somewhere in your code you have to create a window and bind it to the viewmodel. This is the place where the conductor should be created to manage the relationship (Caliburn has this in its IWindowManager implementation which provides and binds Window instances to a given VM when the ShowPopup/ShowDialog methods are called)
The conductor may look like (a contrived example):
public class WindowConductor
{
private ISupportClose _closeable;
private Window _window;
private bool _closingFromViewModel;
private bool _closingFromView;
public WindowConductor(Window view, ISupportClose closeable)
{
_closeable = closeable;
_window = view;
_window.Closed += WindowClosed;
_closeable.Closed += ViewModelClosed;
}
public void WindowClosed(object sender, EventArgs e)
{
if(_closingFromViewModel) return;
_closingFromView = true;
closeable.Close();
}
public void ViewModelClosed(object sender, EventArgs e)
{
if(_closingFromView) return;
_closingFromViewModel = true;
window.Close();
}
}
Your ISupportClose interface can simply be:
public interface ISupportClose
{
event EventHandler<CloseEventArgs> Closed;
void Close();
}
Then when you create your windows to display a view for a VM:
public void CreateWindow(viewModel)
{
Window window = new Window();
window.DataContext .. // etc etc bind up the view/model
// Wrap the window/vm with the conductor if the view supports the interface
var closeable = viewModel as ISupportClose;
if(closeable != null)
new WindowConductor(window, closeable);
}
I always find this very useful as it splits the concerns into smaller chunks. You don't often use more than 1 maybe 2 window implementations in an app anyway.
It may be worth noting that there is a bit of plumbing code behind all this (in fact a base class Screen provides a standard implementation of lifecycle management etc)
If you aren't using an MVVM framework, I'd highly recommend you do so - writing boilerplate 'glue' has been done already by multiple frameworks
The very nature of MVVM stipulates that the model knows nothing about the window that's reading it.
On solution is that the view model throws an event for the Window code to handle.
In your view model code:
public event EventHandler CallOkayRequested;
void CallOkay()
{
var dg = this.CallOkayRequested;
if(dg != null)
{
dg(this, EventArgs.Empty);
}
}
And in your window code, handle this event:
MyMessageBox()
{
InitializeComponent();
((MessageBoxViewModel)this.DataContext).CallOkayRequested += ModelCallOkayRequested;
}
void ModelCallOkayRequested(object sender, EventArgs args)
{
this.Close();
}
This might be the best way to do it, if, for example, the View Model is performing some other actions before wanting the dialog to close.
If, however, the view model is doing nothing other than relaying the request, it's less code if you bypass the model altogether and use a standard RoutedUICommand.
In your XAML declare a command binding:
<Window.CommandBindings>
<CommandBinding Command="ApplicationCommands.Close" Executed="CloseCommandExecuted" />
</Window.CommandBindings>
Attach this command to your button:
<Button Command="ApplicationCommands.Close">
Close
</Button>
And handle the close method in your window code:
private void CloseCommandExecuted(object sender, EventArgs args)
{
this.Close();
}
There are many ways as referenced in Sriram Sakthivel's comment. But using view model event is simplest:
public event Action ViewModelClosed;
void CallOkay()
{
if (ViewModelClosed != null) ViewModelClosed();
}
in MessageBox's code behind:
...
MessageBoxViewModel vm = new MessageBoxViewModel();
vm.ViewModelClosed += () => this.Close();
Another way:
I always use a layer of message box in my view like this:
<UserControl>
<Grid>
<Border>
<!-- contents of my control -->
</Border>
<Border Visibility="{Binding IsVisible,
Converter={StaticResource BooleanToVisibilityConverter}}"
Background="#4000">
<!-- contents of my message box -->
</Border>
</Grid>
</UserControl>
Add a boolean (IsVisible) property to MessageBoxViewModel and bind the Visibility of MessageBox to it. Then simply change its value in CallOkay()
Related
I'm using Material Design for WPF to show a dialog which receives some inputs from the user, and I would like to return a value when it's closed. Here is the sample code:
VM's method that opens the dialog
private async void OnOpenDialog()
{
var view = new TakeInputDialogView();
var result = await DialogHost.Show(view, "RootDialog", ClosingEventHandler);
}
Dialog's VM code
public class TakeSomeInputDialogViewModel : ViewModelBase
{
private string _name;
public string Name
{
get => _name;
set
{
SetProperty(ref _name, value);
SaveCommand.RaiseCanExecuteChanged();
}
}
public bool IsNameInvalid => CanSave();
public DelegateCommand SaveCommand { get; }
public TakeSomeInputDialogViewModel()
{
SaveCommand = new DelegateCommand(OnSave, CanSave);
}
private void OnSave()
{
DialogHost.Close("RootDialog");
}
private bool CanSave()
{
return !string.IsNullOrEmpty(Name);
}
}
When the user clicks save I would like to return Name or some object that will be constructed depending on the user's input.
Side note: I'm also using Prism library, but decided to go with Material Design dialog because I can't locate the dialog in a correct place, when I open the dialog via prism I could only open it in the center of screen or in the center of owner, but I have a single window which hosts sidebar, menu control and content control, and I need to open the dialog in the middle of content control which I wasn't able to achieve.
P.S: I could bind the DataContext of the Dialog to the VM that opens it, but I might have many dialogs and the code might grow too big.
Never show a dialog from the View Model. This is not necessary and will eliminate the benefits MVVM gives you.
Rule of thumb
The MVVM dependency graph:
View ---> View Model ---> Model
Note that the dependencies are on application level. They are component dependencies and not class dependencies (although class dependencies derive from the constraints introduced by the component dependencies).
The above dependency graph translates to the following rules:
In MVVM the View Model does not know the View: the View is nonexistent.
Therefore, if the View is nonexistent for the View Model it is not aware of UI: the View Model is View agnostic.
As a consequence, the View Model has no interest in displaying dialogs. It doesn't know what a dialog is.
A "dialog" is an information exchange between two subjects, in this case the user and the application.
If the View Model is View agnostic it also has no idea of the user to have a dialog with.
The View Model doesn't show dialog controls nor does it handle their flow.
Code-behind is a compiler feature (partial class).
MVVM is a design pattern.
Because by definition a design pattern is language and compiler agnostic, it doesn't rely on or require language or compiler details.
This means a compiler feature can never violate a design pattern.
Therefore code-behind can't violate MVVM.
Since MVVM is a design pattern only design choices can violate it.
Because XAML doesn't allow to implement complex logic we will always have to come back to C# (code-behind) to implement it.
Also the most important UI design rule is to prevent the application from collecting wrong data. You do this by:
a) don't show input options that produce an invalid input in the UI. For example remove or disable the invalid items of a ComboBox, so taht the user can only select valid items.
b) use data validation and the validation feedback infrastructure of WPF: implement INotifyDataErrorInfo to let the View signal the user that his input is invalid (e.g. composition of a password) and requires correction.
c) use file picker dialogs to force the user to provide only valid paths to the application: the user can only pick what really exists in the filesystem.
Following the above principles
will eliminate the need of your application to actively interact with the user (to show dialogs) for 99% of all cases.
In a perfect application all dialogs should be input forms or OS controlled system dialogs.
ensure data integrity (which is even more important).
Example
The following complete example shows how to show dialogs without violating the MVVM design pattern.
It shows two cases
Show a user initiated dialog ("Create User")
Show an application initiated dialog (HTTP connection lost)
Because most dialogs have an "OK" and a "Cancel" button or only a "OK" button, we can easily create a single and reusable dialog.
This dialog is named OkDialog in the example and extends Window.
The example also shows how to implement a way to allow the application to actively communicate with the user, without violating the MVVM design rules.
The example achieves this by having the View Model expose related events that the View can handle. For example, the View can decide to show a dialog (e.g., a message box). In this example, the View will handle a ConnectionLost event raised by a View Model class. The View handles this event by showing a notification to the user.
Because Window is a ContentControl we make use of the ContentContrl.ContentTemplate property:
when we assign a data model to the Window.Content property and a corresponding DataTemplate to the Window.ContentTemplate property, we can create individually designed dialogs by using a single dialog type (OkDialog) as content host.
This solution is MVVM conform because View and View Model are still well separated in term of responsibilities.
Every solution is fine that follows the pattern WPF uses to display validation errors (event, exception or Binding based) or progress bars (usually Binding based).
It's important to ensure that the UI logic does not bleed into the View Model.
The View Model should never wait for a user response. Just like data validation doesn't make the View Model wait for valid input.
The example is easy to convert to support the Dependency Injection pattern.
Reusable key classes of the pattern
DialogId.cs
public enum DialogId
{
Default = 0,
CreateUserDialog,
HttpConnectionLostDialog
}
IOkDialogViewModel.cs
// Optional interface. To be implemented by a dialog view model class
interface IOkDialogViewModel : INotifyPropertyChanged
{
// The title of the dialog
string Title { get; }
// Use this to validate the current view model state/data.
// Return 'false' to disable the "Ok" button.
// This method is invoked by the OkDialog before executing the OkCommand.
bool CanExecuteOkCommand();
// Called after the dialog was successfully closed
void ExecuteOkCommand();
}
OkDialog.xaml.cs
public partial class OkDialog : Window
{
public static RoutedCommand OkCommand { get; } = new RoutedCommand("OkCommand", typeof(MainWindow));
public OkDialog(object contentViewModel)
{
InitializeComponent();
var okCommandBinding = new CommandBinding(OkDialog.OkCommand, ExecuteOkCommand, CanExecuteOkCommand);
_ = this.CommandBindings.Add(okCommandBinding);
this.DataContext = contentViewModel;
this.Content = contentViewModel;
this.DataContextChanged += OnDataContextChanged;
}
// If there is no explicit Content, use the DataContext
private void OnDataContextChanged(object sender, DependencyPropertyChangedEventArgs e) => this.Content ??= e.NewValue;
// If the content view model doesn't implement the optional IOkDialogViewModel just enable the command source.
private void CanExecuteOkCommand(object sender, CanExecuteRoutedEventArgs e)
=> e.CanExecute = (this.Content as IOkDialogViewModel)?.CanExecuteOkCommand() ?? true;
private void ExecuteOkCommand(object sender, ExecutedRoutedEventArgs e)
=> this.DialogResult = true;
}
OkDialog.xaml
Window Height="450" Width="800"
Title="{Binding Title}">
<Window.Template>
<ControlTemplate TargetType="Window">
<Grid>
<Grid.RowDefinitions>
<RowDefinition /> <!-- Content row (dynamic) -->
<RowDefinition Height="Auto" /> <!-- Dialog button row (static) -->
</Grid.RowDefinitions>
<!-- Dynamic content -->
<ContentPresenter Grid.Row="0" />
<StackPanel Grid.Row="1"
Orientation="Horizontal"
HorizontalAlignment="Right">
<Button Content="Ok"
IsDefault="True"
Command="{x:Static local:OkDialog.OkCommand}" />
<Button Content="Cancel"
IsCancel="True" /> <!-- Setting 'IsCancel' to 'true' will automaitcally close the dialog on click -->
</StackPanel>
</Grid>
</ControlTemplate>
</Window.Template>
</Window>
Helper classes to complete the example
MainWindow.xaml.cs
The dialog is always displayed from a component of the View.
partial class MainWindow : Window
{
// By creating a RoutedCommand, we conveniently enable every child control of this view to invoke the command.
// Based on the CommandParameter, this view will decide which dialog or dialog content to load.
public static RoutedCommand ShowDialogCommand { get; } = new RoutedCommand("ShowDialogCommand", typeof(MainWindow));
// Map dialog IDs to a view model class type
private Dictionary<DialogId, Type> DialogIdToViewModelMap { get; }
public MainWindow()
{
InitializeComponent();
var mainViewModel = new MainViewModel();
// Show a notification dialog to the user when the HTTP connection is down
mainViewModel.ConnectionLost += OnConnectionLost;
this.DataContext = new MainViewModel();
this.DialogIdToViewModelMap = new Dictionary<DialogId, Type>()
{
{ DialogId.CreateUserDialog, typeof(CreateUserViewModel) }
{ DialogId.HttpConnectionLostDialog, typeof(MainViewModel) }
};
// Register the routed command
var showDialogCommandBinding = new CommandBinding(
MainWindow.ShowDialogCommand,
ExecuteShowDialogCommand,
CanExecuteShowDialogCommand);
_ = CommandBindings.Add(showDialogCommandBinding);
}
private void CanExecuteShowDialogCommand(object sender, CanExecuteRoutedEventArgs e)
=> e.CanExecute = e.Parameter is DialogId;
private void ExecuteShowDialogCommand(object sender, ExecutedRoutedEventArgs e)
=> ShowDialog((DialogId)e.Parameter);
private void ShowDialog(DialogId parameter)
{
if (!this.DialogIdToViewModelMap.TryGetValue(parameter, out Type viewModelType)
|| !this.MainViewModel.TryGetViewModel(viewModelType, out object viewModel))
{
return;
}
var dialog = new OkDialog(viewModel);
bool isDialogClosedSuccessfully = dialog.ShowDialog().GetValueOrDefault();
if (isDialogClosedSuccessfully && viewModel is IOkDialogViewModel okDialogViewModel)
{
// Because of data bindng the collected data is already inside the view model.
// We can now notify it that the dialog has closed and the data is ready to process.
// Implementing IOkDialogViewModel is optional. At this point the view model could have already handled
// the collected data via the PropertyChanged notification or property setter.
okDialogViewModel.ExecuteOkCommand();
}
}
private void OnConnectionLost(object sender, EventArgs e)
=> ShowDialog(DialogId.HttpConnectionLostDialog);
}
MainWindow.xaml
<Window>
<Button Content="Create User"
Command="{x:Static local:MainWindow.ShowDialogCommand}"
CommandParameter="{x:Static local:DialogId.CreateUserDialog}"/>
</Window>
App.xaml
The implicit DataTemplate for the content of the OkDialog.
<ResourceDictionary>
<!-- The client area of the dialog content.
"Ok" and "Cancel" button are fixed and not part of the client area.
This enforces a homogeneous look and feel for all dialogs -->
<DataTemplate DataType="{x:Type local:CreateUserViewModel}">
<TextBox Text="{Binding UserName}" />
</DataTemplate>
<DataTemplate DataType="{x:Type local:MainViewModel}">
<TextBox Text="HTTP connection lost." />
</DataTemplate>
UserCreatedEventArgs.cs
public class UserCreatedEventArgs : EventArgs
{
public UserCreatedEventArgs(User createdUser) => this.CreatedUser = createdUser;
public User CreatedUser { get; }
}
CreateUserViewModel.cs
// Because this view model wants to be explicitly notified by the dialog when it closes,
// it implements the optional IOkDialogViewModel interface
public class CreateUserViewModel :
IOkDialogViewModel,
INotifyPropertyChanged,
INotifyDataErrorInfo
{
// UserName binds to a TextBox in the dialog's DataTemplate. (that targets CreateUserViewModel)
private string userName;
public string UserName
{
get => this.userName;
set
{
this.userName = value;
OnPropertyChanged();
}
}
public string Title => "Create User";
private DatabaseRepository Repository { get; } = new DatabaseRepository();
bool IOkDialogViewModel.CanExecuteOkCommand() => this.UserName?.StartsWith("#") ?? false;
void IOkDialogViewModel.ExecuteOkCommand()
{
var newUser = new User() { UserName = this.UserName };
// Assume that e.g. the MainViewModel observes the Repository
// and gets notified when a User was created or updated
this.Repository.SaveUser(newUser);
OnUserCreated(newUser);
}
public event EventHandler<UserCreatedEventArgs> UserCreated;
public event PropertyChangedEventHandler? PropertyChanged;
protected virtual void OnUserCreated(User newUser)
=> this.UserCreated?.Invoke(this, new UserCreatedEventArgs(newUser));
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = "")
=> this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
MainViewModel.cs
class MainViewModel : INotifyPropertyChanged
{
public CreateUserViewModel CreateUserViewModel { get; }
public event EventHandler ConnectionLost;
private Dictionary<Type, object> ViewModels { get; }
private HttpService HttpService { get; } = new HttpService();
public MainViewModel()
{
this.CreateUserViewModel = new CreateUserViewModel();
// Handle the created User (optional)
this.CreateUserViewModel.UserCreated += OnUserCreated;
this.ViewModels = new Dictionary<Type, object>
{
{ typeof(CreateUserViewModel), this.CreateUserViewModel },
{ typeof(MainViewModel), this },
};
}
public bool TryGetViewModel(Type viewModelType, out object viewModel)
=> this.ViewModels.TryGetValue(viewModelType, out viewModel);
private void OnUserCreated(object? sender, UserCreatedEventArgs e)
{
User newUser = e.CreatedUser;
}
private void SendHttpRequest(Uri url)
{
this.HttpService.ConnectionTimedOut += OnConnectionTimedOut;
this.HttpService.Send(url);
this.HttpService.ConnectionTimedOut -= OnConnectionTimedOut;
}
private void OnConnectionTimedOut(object sender, EventArgs e)
=> OnConnectionLost();
private void OnConnectionLost()
=> this.ConnectionLost?.Invoke(this, EventArgs.Empt8y);
}
User.cs
class User
{
public string UserName { get; set; }
}
DatabaseRepository.cs
class DatabaseRepository
{}
HttpService.cs
class HttpService
{
public event EventHandler ConnectionTimedOut;
}
You only really need one window in a wpf application, because a window is a content control.
You can template out the entire content using an approach called viewmodel first and data templating.
That window would look like:
<Window ....
Title="{Binding Title}"
Content="{Binding}"
>
</Window>
You would then have a base window viewmodel which exposes a title property.
When you present a viewmodel to an instance of this window you'd by default just see the .ToString() of that viewmodel appear as content. To make it give you some controls you need a datatemplate.
You associate viewmodel type with control type using DataType.
Put all your markup in usercontrols. This includes your markup for mainwindow. At startup you can show an empty instance of TheOnlyWindowIneed then set datacontext asynchronously.
Then go get any data or do anything expensive you need. Once a skeleton mainwindow is up and visible. Showing a busy indicator.
Your datatemplates would all go in a resource dictionary which is merged in app.xaml.
An example
<DataTemplate DataType="{x:Type local:MyBlueViewModel}">
<local:MyBlueUserControl/>
</DataTemplate>
If you now do
var win = new TheOnlyWindowIneed { Content = new MyBlueViewModel() };
win.Owner=this;
win.ShowDialog();
Then you get a dialog shown which has the window that code is in as a parent. The datacontext is your MyBlueViewModel and your entire dialog is filled with a MyBlueUserControl.
You probably want Yes/No buttons and you probably want some standardised UI.
ConfirmationUserControl can look like:
<UserControl.....
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="32"/>
</Grid.RowDefinitions>
<ContentPresenter Content="{Binding ConfirmationMessage}"
Grid.Row="1"/>
<Button HorizontalAlignment="Left"
Content="Yes"
Command="{Binding YesCommand}"
/>
<Button HorizontalAlignment="Left"
Content="No"
Command="{Binding NoCommand}"
/>
</Grid>
</UserControl>
ConfirmationViewModel would expose the usual Title, ConfirmationMessage can be Another viewmodel ( and usercontrol pair ). This is then data templated out into UI in a similar fashion.
YesCommand would be a public iCommand which is set from whatever shows the dialog. This passes in whatever logic is to happen when the user clicks Yes. If it's deleting then it has code calls a delete method. It could use a lambda and capture the context of the owning viewmodel or it could have an explicit instance of a class passed in. The latter being more unit test friendly.
The call to this dialog is at the end of some code you have in your viewmodel.
There is no code waiting for the result.
The code that would be is in yescommand.
NoCommand might just close the parent window. By putting code that actions the result in yes command you probably do not need to "know" in the calling code what the user chose. It's already been handled.
You might therefore decide NoCommand uses some generic window closing approach:
public class GenericCommands
{
public static readonly ICommand CloseCommand =
new RelayCommand<Window>(o =>
{
if(o == null)
{
return;
}
if(o is Window)
{
((Window)o).Close();
}
}
);
That needs a reference to the window which is passed in by parameter
Command="{x:Static ui:GenericCommands.CloseCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}"
The viewmodel still needs to somehow initiate showing the window.
You only have the one window though. You can put code in that and initiate showing a window from the one and only window.
All you need at a minimum is a dependency property and a callback.
public partial class TheOnlyWindowIneed : Window
{
public object? ShowAdialogue
{
get
{
return (object?)GetValue(ShowAdialogueProperty);
}
set
{
SetValue(ShowAdialogueProperty, value);
}
}
public static readonly DependencyProperty ShowAdialogueProperty =
DependencyProperty.Register("ShowAdialogue",
typeof(object),
typeof(TheOnlyWindowIneed),
new FrameworkPropertyMetadata(null
, new PropertyChangedCallback(ShowAdialogueChanged)
)
);
private static void ShowAdialogueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var win = new TheOnlyWindowIneed { Content = e.NewValue };
win.Owner = d as TheOnlyWindowIneed;
win.ShowDialog();
}
Here when you bind ShowAdialogue to a property in your viewmodel, when you set that property to an instance of a dialogue viewmodel, it should show a dialog with that viewmodel as datacontext and as explained that will be templated out into UI.
A perhaps more elegant way to handle this would be to use the pub/sub pattern and the community mvvm toolkit messenger. You can send the viewmodel you want to show in a dialogue via messenger, subscribe in mainwindow and act in that handler.
If you just want to show some info then there is another option to consider. If all your users are using win10+ then you can easily use toast. The toast that pops up can have buttons in it but is usually just used to show messages.
Add the nuget package Notifications.wpf
You can show toast in your mainwindow or where notifications usually pop up in win10. In mainwindow:
<toast:NotificationArea x:Name="MainWindowNotificationsArea"
Position="BottomRight"
MaxItems="3"
Grid.Column="1"/>
In a viewmodel:
var notificationManager = new NotificationManager();
notificationManager.Show(
new NotificationContent {
Title = "Transaction Saved",
Message = $"Transaction id {TransactionId}" },
areaName: "MainWindowNotificationsArea");
The view and viewmodel are totally decoupled, you don't need any reference to anything. NotificationContent can be much richer than shown here.
I think your app might also have to target win10 as minimum O/S.
I've been going back and forth over the pros and cons of the two following approaches to Events from custom controls. My debate basically revolves around how much "logic" should be placed within a custom (not user) control and to best get events into a viewmodel.
The "control", DataGridAnnotationControl, resides within an adorner to my data grid. The goal here is to respond to the user selecting an item from a combobox displayed within the custom control.
The first example, Example #1, uses a pretty standard custom event in the DataGridAnnotationControl
which is then mapped by way of the adorner to the target AppointmentEditor (viewmodel). My biggest complaint with this is the obvious dependency to the (AppointmentEditor) from the adorner to achieve proper event routing.
♦ Example #1:
♦ CustomControl DataGridAnnotationControl
public override void OnApplyTemplate()
{
......
_cboLastName.SelectionChanged += _cboLastName_SelectionChanged;
}
private void _cboLastName_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
RaiseSelectionChanged();
}
public event Action SelectionChanged;
public void RaiseSelectionChanged()
{
SelectionChanged?.Invoke();
}
♦ Adorner DataGridAnnotationAdorner
public DataGridAnnotationAdorner(DataGrid adornedDataGrid)
: base(adornedDataGrid)
{
......
Control = new DataGridAnnotationControl();
this.SelectionChanged += ((AppointmentEditor)adornedDataGrid.DataContext).SelectionChanged; <--This requires a reference to Patient_Registration.Editors. THIS IS FORCING
A DEPENDENCY ON THE PATIENT_REGISTRATION PROJECT.
}
public event Action SelectionChanged
{
add { Control.SelectionChanged += value; }
remove { Control.SelectionChanged -= value; }
}
♦ AppointmentEditor
public void SelectionChanged()
{
throw new NotImplementedException();
}
Example #2 This example uses pretty standard event routing up to the mainwindow from which an event aggregator is being used to hit the AppointmentEditor as a subscriber to the event. My biggest complaint here is all the additional code needed (over Example #1). In addition, it seems like a complicating factor to climb the visual tree just to jump into the one viewmodel designed to support this customcontrol.
Example #2:
♦ CustomControl DataGridAnnotationControl
public override void OnApplyTemplate()
{
.....
_cboLastName.SelectionChanged += _cboLastName_SelectionChanged;
}
private void _cboLastName_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
RaisePatientNameSelectionChangedEvent();
}
public static readonly RoutedEvent PatientNameSelectionChangedEvent = EventManager.RegisterRoutedEvent(
"PatientNameSelectionChanged", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(DataGridAnnotationControl));
// Provide CLR accessors for the event
public event RoutedEventHandler PatientNameSelectionChanged
{
add { AddHandler(PatientNameSelectionChangedEvent, value); }
remove { RemoveHandler(PatientNameSelectionChangedEvent, value); }
}
protected virtual void RaisePatientNameSelectionChangedEvent()
{
RoutedEventArgs args = new RoutedEventArgs(DataGridAnnotationControl.PatientNameSelectionChangedEvent);
RaiseEvent(args);
}
♦ public partial class MainWindow : Window
{
public MainWindow(IMainWindowViewModel mainWindowViewModel, EventAggregator eventAggregator)
{
InitializeComponent();
EventAggregator = eventAggregator;
DataContext = mainWindowViewModel;
....
AddHandler(DataGridAnnotationControl.PatientNameSelectionChangedEvent, new RoutedEventHandler(PatientNameSelectionChangedHandler));
}
private void PatientNameSelectionChangedHandler(object sender, RoutedEventArgs e)
{
EventAggregator.PublishEvent( new PatientNameSelected() );
}
}
♦ public class AppointmentEditor : INotifyPropertyChanged, ISubscriber<PatientNameSelected>
public void OnEventHandlerAsync(PatientNameSelected e)
{
throw new NotImplementedException();
}
Is there a preferred way of doing this?
TIA
Ideally, your custom control should have no knowledge of your view-models.
Using MVVM, you would bind an event in your custom control to a command in your view-model.
I author and maintain tons of custom controls that are used by a lot of other teams. I always expose an associated ICommand with any event to make it easy for MVVM users to use my controls in the easiest way possible.
I have a simple WPF page with one text box field that my client wants highlighted when the page shows up. In code behind, it would be three lines, but I'm sogging through MVVM (which I'm starting to think is a little over-rated). I've tried so many different variants of behaviors and global events and FocusManager.FocusedElement, but nothing I do will do this.
Ultimately the most of the code I've been using calls these two lines:
Keyboard.Focus(textBox);
textBox.SelectAll();
But no matter where I put these lines the text box is only focused; no text is selected. I have never had this much trouble with something so simple. I've been hitting my head against the internets for two hours. Does anyone know how to do this?
Again, all I want to do is have the text box focus and it's text all selected when the page is navigated to. Please help!
"Focus" and "Select All Text from a TextBox" is a View-specific concern.
Put that in code Behind. It does not break the MVVM separation at all.
public void WhateverControl_Loaded(stuff)
{
Keyboard.Focus(textBox);
textBox.SelectAll();
}
If you need to do it in response to a specific application/business logic. Create an Attached Property.
Or:
have your View resolve the ViewModel by:
this.DataContext as MyViewModel;
then create some event in the ViewModel to which you can hook:
public class MyViewModel
{
public Action INeedToFocusStuff {get;set;}
public void SomeLogic()
{
if (SomeCondition)
INeedToFocusStuff();
}
}
then hook it up in the View:
public void Window_Loaded(Or whatever)
{
var vm = this.DataContext as MyViewModel;
vm.INeedToFocusStuff += FocusMyStuff;
}
public void FocusMyStuff()
{
WhateverTextBox.Focus();
}
See how this simple abstraction keeps View related stuff in the View and ViewModel related stuff in the ViewModel, while allowing them to interact. Keep it Simple. You don't need NASA's servers for a WPF app.
And no MVVM is not overrated, MVVM is extremely helpful and I would say even necessary. You'll quickly realize this as soon as you begin working with ItemsControls such as ListBoxes or DataGrids.
Here are some workthroughs:
Use Interaction.Behaviors
You can install the NuGet package named Microsoft.Xaml.Behaviors.Wpf, and write your own Behavior:
using Microsoft.Xaml.Behaviors;
using System.Windows;
using System.Windows.Controls;
public class AutoSelectAllBehavior : Behavior<TextBox>
{
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.GotFocus += AssociatedObject_GotFocus;
}
private void AssociatedObject_GotFocus(object sender, RoutedEventArgs e)
{
if (AssociatedObject is TextBox box)
box.SelectAll();
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.GotFocus -= AssociatedObject_GotFocus;
}
}
and attach this behavior to the TextBox in the xaml:
<!-- xmlns:i="http://schemas.microsoft.com/xaml/behaviors" -->
<TextBox>
<i:Interaction.Behaviors>
<br:AutoSelectAllBehavior />
</i:Interaction.Behaviors>
</TextBox>
Use Interaction.Triggers
This is in the same package as mentioned in the last section. This special can be considered to let you be able to bind UIElement events to your ViewModel.
In your ViewModel, suppose you have an ICommand relay command (You may also need Microsoft.Toolkit.MVVM so that you can use some handy relay commands):
public ICommand SelectAllCommand { get; }
public ViewModel()
{
SelectAllCommand = new RelayCommand<TextBox>(box => box.SelectAll());
}
and then attach this command to the TextBox by setting the triggers:
<TextBox>
<i:Interaction.Triggers>
<i:EventTrigger EventName="GotFocus">
<i:InvokeCommandAction Command="{Binding SelectAllCommand}" CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=TextBox}}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
Use Attached Property
You can also use attached property (write your own class derived from TextBox and use dependency property is quite similar):
using System.Windows;
using System.Windows.Controls;
public class TextBoxProperties
{
public static bool GetAutoSelectAll(DependencyObject obj)
{
return (bool)obj.GetValue(AutoSelectAllProperty);
}
public static void SetAutoSelectAll(DependencyObject obj, bool value)
{
obj.SetValue(AutoSelectAllProperty, value);
}
public static readonly DependencyProperty AutoSelectAllProperty =
DependencyProperty.RegisterAttached("AutoSelectAll", typeof(bool), typeof(TextBoxProperties), new PropertyMetadata(false, TextBoxProperties_PropertyChanged));
private static void TextBoxProperties_PropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if ((bool)e.NewValue)
{
if (d is TextBox box)
{
box.GotFocus += TextBox_GotFocus;
}
}
}
private static void TextBox_GotFocus(object sender, RoutedEventArgs e)
{
var box = sender as TextBox;
box.SelectAll();
}
}
Then you can use it like:
<!-- xmlns:ap="..." -->
<TextBox ap:TextBoxProperties.AutoSelectAll="True" />
I'm new to MVVM and trying to figure out how to close a ChildWindow with the traditional Cancel button using MVVM Light Toolkit.
In my ChildWindow (StoreDetail.xaml), I have :
<Button x:Name="CancelButton" Content="Cancel" Command="{Binding CancelCommand}" />
In my ViewModel (ViewModelStoreDetail.cs), I have :
public ICommand CancelCommand { get; private set; }
public ViewModelStoreDetail()
{
CancelCommand = new RelayCommand(CancelEval);
}
private void CancelEval()
{
//Not sure if Messenger is the way to go here...
//Messenger.Default.Send<string>("ClosePostEventChildWindow", "ClosePostEventChildWindow");
}
private DelegateCommand _cancelCommand;
public ICommand CancelCommand
{
get
{
if (_cancelCommand == null)
_cancelCommand = new DelegateCommand(CloseWindow);
return _cancelCommand;
}
}
private void CloseWindow()
{
Application.Current.Windows[Application.Current.Windows.Count - 1].Close();
}
If you displayed your child window by calling ShowDialog(), then you can simply set the IsCancel property of your button control to "True".
<Button Content="Cancel" IsCancel="True" />
It becomes the same as clicking the X button on the window, or pressing ESC on the keyboard.
Have a look at this articleon MSDN. About half way down there is an approach on how to do this. Basically it uses either uses a WorkspaceViewModel or you implements an interface that exposes and event RequestClose
You then inside the Window's DataContext (if you are setting the ViewModel to it) you can attach to the event.
This is an excerpt from the article (Figure 7). You can adjust it to suit your needs.
// In App.xaml.cs
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
MainWindow window = new MainWindow();
// Create the ViewModel to which
// the main window binds.
string path = "Data/customers.xml";
var viewModel = new MainWindowViewModel(path);
// When the ViewModel asks to be closed,
// close the window.
viewModel.RequestClose += delegate
{
window.Close();
};
// Allow all controls in the window to
// bind to the ViewModel by setting the
// DataContext, which propagates down
// the element tree.
window.DataContext = viewModel;
window.Show();
}
It's been a while since I've used WPF and MVVMLight but yes I think I'd use the messanger to send the cancel event.
In MVVM Light Toolkit the best what you can do is to use Messenger to interact with the View.
Simply register close method in the View (typically in the code behind file) and then send request to close a window when you need it.
We have implemented a NO-CODE BEHIND functionality. See if it helps.
EDIT: Here is there Stackoverflow discussion
Here are some ways to accomplish it.
Send message to your childwindow and set DialogueResult to false on childwindow code-behind.
Make property of DialogueResult and Bind it with childwindow Dialoue CLR property, set it on CancelEval method of CancelCommand.
Create object of Childwindow and set DialogueResult false on CancelEval.
Kind of late to the party but I thought I'd add my input. Borrowing from user841960's answer:
public RelayCommand CancelCommand
{
get;
private set;
}
Then:
SaveSettings = new RelayCommand(() => CloseWindow());
Then:
private void CloseWindow()
{
Application.Current.Windows[Application.Current.Windows.Count - 1].Close();
}
It's a bit cleaner than using an ICommand and works just as well.
So, to sum it all up, the example class would look like so:
public class ChildViewModel
{
public RelayCommand CancelCommand
{
get;
private set;
}
public ChildViewModel()
{
SaveSettings = new RelayCommand(() => CloseWindow());
}
private void CloseWindow()
{
Application.Current.Windows[Application.Current.Windows.Count - 1].Close();
}
}
I have a custom control defined using WPF in a independent assembly. In another project, I simply just reference it and use it in the XAML like this:
<my:CustomUserControl Name="myControl" IsEnabled="{Binding CanTheUserInputTrade}"/>
The CustomUserControl class has a member function called "Reset".
I used to call this function inside View.xaml.cs file using:
myControl.Reset()
However, for a valid reason, I have to move the logic of calling this function into the ViewModel. As far as I know, it is not a good practice to have a reference to the view in the ViewModel. Therefore I won't be able to access the "myControl" reference from the ViewModel.
My question is: How can I call the Reset function within the ViewModel.
This is a common use case and I am sure there is a way to do this. Can someone point me to the right direction.
Many thanks.
In the past I have hooked up the event from within the View's code-behind.
ViewModel:
public ICommand ResetCommand {get; set;}
From UserControl's OnLoad method:
private void MyUserControl_Loaded(object sender, RoutedEventArgs e)
{
MyUserControl ctrl = sender as MyUserControl;
if (ctrl == null) return;
MyViewModel vm = ctrl.DataContext as MyViewModel ;
if (vm == null)
return;
vm.ResetCommand = new RelayCommand(param => this.Reset());
}
#Rachel's solution is great. Using an interface makes it a little more loosely coupled:
using System.Windows.Input;
namespace WpfApplication
{
public interface IResetCaller
{
ICommand ResetCommand { get; set; }
}
}
Have your base View Model implement this interface, e.g.
public class MyViewModel : ModelBase, IResetCaller
{
...
public ICommand RefreshSegmentCommand { get; set; }
}
And Rachel's code becomes:
private void MyUserControl_Loaded(object sender, RoutedEventArgs e)
{
var ctrl = sender as FrameworkElement;
if (ctrl == null) return;
var vm = ctrl.DataContext as IResetCaller;
if (vm == null)
return;
vm.ResetCommand = new RelayCommand(param => this.Reset(param));
}
This interface could be used to decorate any number of view models, and the interface can be defined in the same library as the UserControl. In the main ViewModel, you'd simply do something like ResetCommand.Execute(this) or whatever param you'd like to pass.