I have a WPF window which use heavy library and takes time to be fully rendered.
This library is in an UserControl.
This window is open by a context menu command in the parent window.
Using MVVM pattern, I need to get the DialogResult of this new window when closing to access the viewmodel.
When clicking the context menu item to open this new window, the context menu stays open until the instanciation of the new window will be done.
What can I do to close the context menu before open this window?
Here is the code refactored with the help of BionicCode:
MAIN WINDOW XAML
<Image Source="{Binding ImagePath}" Height="100" Width="100">
<Image.ContextMenu>
<ContextMenu>
<MenuItem Header="Open Window"
Command="{x:Static local:MainWindow.ShowMyDialogCommand}"
CommandTarget="{Binding RelativeSource={RelativeSource AncestorType=ContextMenu}, Path=PlacementTarget}"
/>
</ContextMenu>
</Image.ContextMenu>
</Image>
MAIN WINDOW
public partial class MainWindow : Window
{
public static RoutedCommand ShowMyDialogCommand { get; } = new RoutedCommand("ShowMyDialogCommand", typeof(MainWindow));
private readonly MainVM myMainVM;
public MainWindow()
{
InitializeComponent();
myMainVM = new MainVM();
DataContext = myMainVM;
var showMyDialogCommandBinding = new CommandBinding(ShowMyDialogCommand, ExecuteShowMyDialogCommand, CanExecuteShowMyDialogCommand);
this.CommandBindings.Add(showMyDialogCommandBinding);
}
private void CanExecuteShowMyDialogCommand(object sender, CanExecuteRoutedEventArgs e) => e.CanExecute = true;
private void ExecuteShowMyDialogCommand(object sender, ExecutedRoutedEventArgs e)
{
ViewerVM vm = new ViewerVM();
var okDialog = new OkDialog()
{
Title = "Viewer Dialog",
DataContext = vm
};
bool? dialogResult = okDialog.ShowDialog();
if (dialogResult == true)
{
this.myMainVM.HandleData(vm);
}
}
}
MAIN VM
public class MainVM : ObservableObject
{
private string myImagePath;
public MainVM()
{
myImagePath = "flower.jpg";
}
public string ImagePath
{
get { return myImagePath; }
set
{
if (myImagePath == value) return;
myImagePath = value;
OnPropertyChanged(nameof(ImagePath));
}
}
public void HandleData(ViewerVM viewModel)
{
//Do stuffs
}
}
NEW WINDOW XAML
<Window.Template>
<ControlTemplate TargetType="Window">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<!-- Dynamic content row -->
<RowDefinition Height="Auto" />
<!-- Static content row (ok and cancel buttons etc.) -->
</Grid.RowDefinitions>
<!-- Dynamic content -->
<ContentPresenter Grid.Row="0" />
<!-- Static content -->
<StackPanel Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Right">
<Button Content="Ok" IsDefault="True" Command="{x:Static local:OkDialog.OkCommand}" />
<Button Content="Cancel" IsCancel="True" />
</StackPanel>
</Grid>
</ControlTemplate>
</Window.Template>
NEW WINDOW
public partial class OkDialog : Window
{
public static RoutedCommand OkCommand { get; } = new RoutedCommand("OkCommand", typeof(MainWindow));
public OkDialog()
{
InitializeComponent();
var okCommandBinding = new CommandBinding(OkDialog.OkCommand, ExecuteOkCommand, CanExecuteOkCommand);
this.CommandBindings.Add(okCommandBinding);
this.DataContextChanged += OnDataContextChanged;
}
// If there is no explicit Content set, use the DataContext
private void OnDataContextChanged(object sender, DependencyPropertyChangedEventArgs e) => this.Content = e.NewValue;
private void CanExecuteOkCommand(object sender, CanExecuteRoutedEventArgs e)
=> e.CanExecute = (this.DataContext as IOkDialogVM).CanExecuteOkCommand() ? true : false;
private void ExecuteOkCommand(object sender, ExecutedRoutedEventArgs e)
=> this.DialogResult = true;
}
interface IOkDialogVM
{
bool CanExecuteOkCommand();
}
UserControl
<UserControl x:Class="ContextMenuTest.Viewer"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:ddes="http://schemas.devdept.com/winfx/2008/xaml/control"
xmlns:ddgr="http://schemas.devdept.com/winfx/2008/xaml/graphics"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<ddes:Design x:Name="myDesigner" Height="300" Width="300">
<ddes:Design.Viewports>
<ddes:Viewport>
<ddes:Viewport.Background>
<ddgr:BackgroundSettings StyleMode="Solid" TopColor="White"/>
</ddes:Viewport.Background>
</ddes:Viewport>
</ddes:Design.Viewports>
</ddes:Design>
</Grid>
</UserControl>
public partial class Viewer : UserControl
{
public Viewer()
{
InitializeComponent();
}
}
public class ViewerVM : ObservableObject, IOkDialogVM
{
public bool CanExecuteOkCommand() => true;
}
App.xaml
<Application.Resources>
<DataTemplate DataType="{x:Type local:ViewerVM}">
<local:Viewer/>
</DataTemplate>
</Application.Resources>
Your current code breaks the MVVM design pattern. This is because you are managing views in your View Model. The view model class has no idea that the view will show a dialog. It therefore doesn't participate in any dialog flow.
You control the dialog completely in the View. You show it and you close it without any dependency on a view model class.
When you make use of the Button.IsCancel property the Window will close itself without the need to attach any event handler or close commands to this Button.
Setting the Window.DialogResult will always close the Window and let the Window.ShowDialog return the Window.DialogResult. You only need to attach an event handler to set the Window.DialogResult to true or false.
Window will take care of the rest. It's as easy as it can get. No View Model needed.
To show a dialog in an MVVM application, you can follow the below examples in the sections: MVVM compliant dialog flow and Advanced MVVM compliant dialog flow.
To fix the loading experience, you shouldn't create any views in the constructor. Only do some light work in the constructor so that the constructor can return fast.
As a general rule, you should always avoid creating controls in your code-behind to add them manually to the visual tree. This is done in XAML, which wouldn't cause your current issue in the first place.
If you really need to do it your way, chose to create the views either in the FrameworkElement.Loaded event or override the FrameworkElement.OnApplyTemplate method.
Because of the heavy load, I suggest to move your code to the Loaded event handler.
It's unclear what your DesignView constructor is exactly doing. In case you have shown the complete constructor and the timing of the call of the following line
devDept.LicenseManager.Unlock(typeof(devDept.Eyeshot.Workspace), "mykey");
doesn't matter or can be deferred, you should move this line to the Loaded event handler too. Just in case LicenseManager.Unlock is the blocking piece.
public partial class PartEditView : UserControl
{
private DesignerView myDesignerView;
public PartEditView()
{
InitializeComponent();
// Follow this pattern to unlock the DesignerView.
this.Loaded += OnLoaded;
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
this.myDesignerView = new DesignerView();
this.myDesignerContainer.Children.Add(myDesignerView);
}
}
MVVM compliant dialog flow
The idea is simple, your View is responsible to show the dialog. Data is displayed/collected by binding elements to a dedicated view model of the dialog. After the dialog was closed, the View can interact with the View Model to pass over the data. In most scenarios the view model of the dialog knows how to handle the data (for example how to use the Model to persist data).
MainWindowViewModel.cs
The view model class has no idea that the view will show a dialog.
It doesn't participate in any dialog flow.
If the view model must handle the data collected by a dialog, the responsible view can pass the data to the view model.
class MainWindowViewModel : INotifyPropertyChanged
{
// Such a public method is one possible way to allow the view to pass data
// to this instance. Simply use the common means to send data from View to View Model.
public void HandleData(MyDialogViewModel viewModel)
{
}
}
MainWindow.xaml.cs
partial class MainWindow : Window
{
public static RoutedCommand ShowMyDialogCommand { get; } = new RoutedCommand("ShowMyDialogCommand", typeof(MainWindow));
private MainWindowViewModel MainWindowViewModel { get; }
public MainWindow()
{
InitializeComponent();
this.MainWindowViewModel = new MainWindowViewModel();
this.DataContext = this.MainWindowViewModel;
var showMyDialogCommandBinding = new CommandBinding(ShowMyDialogCommand, ExecuteShowMyDialogCommand, CanExecuteShowMyDialogCommand);
this.CommandBindings.Add(showMyDialogCommandBinding);
}
private void CanExecuteShowMyDialogCommand(object sender, CanExecuteRoutedEventArgs e) => e.CanExecute = true;
private void ExecuteShowMyDialogCommand(object sender, ExecutedRoutedEventArgs e)
{
var myDialogViewModel = new MyDialogViewModel();
var myDialog = new MyDialog()
{
Content = "I'm a dialog",
DataContext = myDialogViewModel
};
bool? dialogResult = myDialog.ShowDialog();
// Do something when the user has closed the dialog e.g. using the 'OK' button
if (dialogResult == true)
{
// Pass the dialog data (if it has some) to the view model class
// for further processing. The data is stored via data binding in the
// myDialogViewModel (the DataContext of the dialog).
// Depending on the context of the dialog, the dialog's view model
// knows what to do with the data (e.g. save it to a database using the Model).
this.MainWindowViewModel.HandleDialogData(myDialogViewModel);
}
}
}
MainWindow.xaml
Because the ContextMenu will have its own visual tree (it uses a Popup to display content), the routed command must be executed in the visual tree of the parent Window. For this reason we must explicitly set the MenuItem.CommandTarget property to point to the visual tree outside of the ContextMenu. The CommandTarget will therefore point to the ContextMenu.PlacementTarget (which is the Image in the example). The Image is an element of the Window visual tree where the CommandBinding is defined.
This is only necessary when the routed command is used inside a Popup (for example ContextMenu).
Otherwise setting the CommandTarget is not necessary.
<Window>
<StackPanel>
<!-- CommandTarget is not needed when the ICommandSource is part of the parent Window's visual tree -->
<Button Command="{x:Static local:MainWindow.ShowMyDialogCommand}" />
<Image>
<Image.ContextMenu>
<ContextMenu>
<!-- Visual tree is different from the Window (due to the Popup).
Set CommandTarget to allow the command to traverse the visual tree
of the MainWindow to reach to the CommandBindng (defined by the MainWindow) -->
<MenuItem Header="Open Window"
Command="{x:Static local:MainWindow.ShowMyDialogCommand}"
CommandTarget="{Binding RelativeSource={RelativeSource
AncestorType=ContextMenu}, Path=PlacementTarget}" />
</ContextMenu>
</Image.ContextMenu>
</Image>
</StackPanel>
</Window>
MyDialog.xaml.cs
partial class MyDialog : Window
{
public MainWindow()
{
InitializeComponent();
}
private void OkButton_Click(object sender, RoutedEventArgs e)
{
// Setting the DialogResult will automatically close the Window
// and return the DialogResult value.
this.DialogResult = true;
}
}
MyDialog.xaml
It's important to set Button.IsCancel to true for the "Cancel" button.
This allows the Window to close itself automatically.
Closing the Window in case of the "Ok" button being clicked is achieved by setting the Window.DialogResult property from a Button.Click handler (or RoutedCommand). Window will always close itself when Window.DialogResult is set.
<Window>
<Grid>
<Grid.RowDefinitions>
<RowDefinition /> <!-- Content row -->
<RowDefinition Height="Auto" /> <!-- Dialog button row -->
</Grid.RowDefinitions>
<TextBlock Grid.Row="0"
Text="I'm a custom dialog" />
<StackPanel Grid.Row="1"
Orientation="Horizontal"
HorizontalAlignment="Right">
<Button Content="Ok"
IsDefault="True"
Click="OkButton_Click"/>
<Button Content="Cancel"
IsCancel="True" />
</StackPanel>
</Grid>
</Window>
Advanced MVVM compliant dialog flow
A more advanced version will make use of the fact that the Window is a ContentControl. This means we can define the content based on a data model (like the above MyDialogViewModel) and load the associated view by defining a DataTemplate, preferably implicit (without the x:Key directive defined). This makes the dialog highly reusable and easy to deal with in an MVVM context.
The following example defines a dialog that only knows how to handle an "Ok" and "Cancel" button. But through data templating the same class can show all kind of views.
IOkDialogViewModel.cs
interface IOkDialogViewModel
{
bool CanExecuteOkCommand();
}
OkDialogViewModel.cs
Example data model that is mapped to a dedicated view via a DataTemplate
that makes the content of the dialog.
// Consider to implement INotifyDataErrorInfo
public class OkDialogViewModel : IOkDialogViewModel, INotifyPropertyChanged
{
public string SomeText { get; set; }
public event PropertyChangedEventHandler? PropertyChanged;
public bool CanExecuteOkCommand() => this.SomeText.StartsWith("#");
}
OkDialog.xaml.cs
public partial class OkDialog : Window
{
public static RoutedCommand OkCommand { get; } = new RoutedCommand("OkCommand", typeof(MainWindow));
public OkDialog()
{
InitializeComponent();
var okCommandBinding = new CommandBinding(OkDialog.OkCommand, ExecuteOkCommand, CanExecuteOkCommand);
this.CommandBindings.Add(okCommandBinding);
this.DataContextChanged += OnDataContextChanged;
}
// If there is no explicit Content set, use the DataContext
private void OnDataContextChanged(object sender, DependencyPropertyChangedEventArgs e) => this.Content ??= e.NewValue;
private void CanExecuteOkCommand(object sender, CanExecuteRoutedEventArgs e)
=> e.CanExecute = (this.DataContext as IOkDialogViewModel)?.CanExecuteOkCommand() ?? true;
private void ExecuteOkCommand(object sender, ExecutedRoutedEventArgs e)
=> this.DialogResult = true;
}
OkDialog.xaml
Now hardcode the default content (the "Ok" and "Close" buttons) into the Window.Template. This will make the static content.
The dynamic content is implicitly created by the client who defined a DataTemplate for the Window.Content.
<Window>
<Window.Template>
<ControlTemplate TargetType="Window">
<Grid>
<Grid.RowDefinitions>
<RowDefinition /> <!-- Dynamic content row -->
<RowDefinition Height="Auto" /> <!-- Static content row (ok and cancel buttons etc.) -->
</Grid.RowDefinitions>
<!-- Dynamic content -->
<ContentPresenter Grid.Row="0" />
<!-- Static content -->
<StackPanel Grid.Row="1"
Orientation="Horizontal"
HorizontalAlignment="Right">
<Button Content="Ok"
IsDefault="True"
Command="{x:Static local:OkDialog.OkCommand}" />
<Button Content="Cancel"
IsCancel="True" />
</StackPanel>
</Grid>
</ControlTemplate>
</Window.Template>
</Window>
App.xaml
Define a DataTemplate to crate the particular dialog view that is associated with the OkDialogViewModel.
<Application>
<Application.Resources>
<DataTemplate DataType="{x:Type local:OkDialogViewModel}">
<TextBox Text="{Binding SomeText}" />
</DataTemplate>
</Application.Resources>
</Application>
MainWindow.xaml.cs
partial class MainWindow : Window
{
public static RoutedCommand ShowMyDialogCommand { get; } = new RoutedCommand("ShowMyDialogCommand", typeof(MainWindow));
private MainWindowViewModel MainWindowViewModel { get; }
public MainWindow()
{
InitializeComponent();
this.MainWindowViewModel = new MainWindowViewModel();
this.DataContext = this.MainWindowViewModel;
var showMyDialogCommandBinding = new CommandBinding(ShowMyDialogCommand, ExecuteShowMyDialogCommand, CanExecuteShowMyDialogCommand);
this.CommandBindings.Add(showMyDialogCommandBinding);
}
private void CanExecuteShowMyDialogCommand(object sender, CanExecuteRoutedEventArgs e) => e.CanExecute = true;
private void ExecuteShowMyDialogCommand(object sender, ExecutedRoutedEventArgs e)
{
// Because the text doesn't start with '#', the OK button will be disabled later,
// until the user fixes the input in the TextBox.
var dialogViewModel = new OkDialogViewModel() { SomeText = "Just some text" };
var okDialog = new OkDialog()
{
Title = "I'm an Ok dialog",
DataContext = dialogViewModel
};
bool? dialogResult = okDialog.ShowDialog();
// Do something when the user has closed the dialog e.g. using the 'OK' button
if (dialogResult == true)
{
// Pass the dialog data (if it has some) to the view model class
// for further processing. The data is stored via data binding in the
// DataContext/Content of the dialog.
// Depending on the context of the dialog, the dialog's view model
// knows what to do with the data (e.g. save it to a database using the Model).
this.MainWindowViewModel.HandleData(dialogViewModel);
}
}
}
MainWindow.xaml
Because the ContextMenu will have its own visual tree (it uses a Popup to display content), the routed command must be executed in the visual tree of the parent Window. For this reason we must explicitly set the MenuItem.CommandTarget property to point to the visual tree outside of the ContextMenu. The CommandTarget will therefore point to the ContextMenu.PlacementTarget (which is the Image in the example). The Image is an element of the Window visual tree where the CommandBinding is defined.
This is only necessary when the routed command is used inside a Popup (for example ContextMenu).
Otherwise setting the CommandTarget is not necessary.
<Window>
<StackPanel>
<!-- CommandTarget is not needed when the ICommandSource is part of the parent Window's visual tree -->
<Button Command="{x:Static local:MainWindow.ShowMyDialogCommand}" />
<Image>
<Image.ContextMenu>
<ContextMenu>
<!-- Visual tree is different from the Window (due to the Popup).
Set CommandTarget to allow the command to traverse the visual tree
of the MainWindow to reach to the CommandBindng (defined by the MainWindow) -->
<MenuItem Header="Open Window"
Command="{x:Static local:MainWindow.ShowMyDialogCommand}"
CommandTarget="{Binding RelativeSource={RelativeSource
AncestorType=ContextMenu}, Path=PlacementTarget}" />
</ContextMenu>
</Image.ContextMenu>
</Image>
</StackPanel>
</Window>
Update
It turns out that the origin is really the 3rd party library. The implementation of the control is obviously really bad. It freezes the UI during construction/loading which is unacceptable.
Because the UI is frozen you can't even show a busy indicator. The user is left to believe that the application has crashed.
Such a library would make me doubt the authors skills and experience.
Because of the serious impact on the application's performance and UX I recommend to find an alternative library.
Even closing the ContextMenu forcefully does not solve the problem of a bad UX as the application still hangs.
The following solution extends the previous "Advanced MVVM compliant dialog flow" example. Following the "Advanced MVVM compliant dialog flow" will give you a clean design that helps to solve the issue more "gracefully" (I still recommend to find a better library).
The solution implements the following flow:
Instead of opening the dialog (which contains the terrible control) directly on clicking the MenuItem, we modify the flow to first close the ContextMenu.
This is accomplished by registering a ContextMenu.Opened event handler.
Next we spawn a second UI thread. Because any busy indicator that runs in the primary UI thread would freeze too, we use this dedicated new UI thread to show a busy indicator dialog. This way we can improve the UX significantly as from the user's point of view everything appears to be under control: just some heavy loading in the background.
In the main UI tread we create the instance of the dialog which is known to freeze the application (which will still freeze)
We use a SemaphoreSlim to allow the busy indicator dialog to wait asynchronously for a signal from the main UI thread in order to continue.
After the busy indicator thread received the signal, the busy indicator will close itself and shut down the second UI thread
The dialog cantaining the 3rd party control is now ready to use.
MainWindow.xaml
<Window>
<Image>
<Image.ContextMenu>
<ContextMenu Closed="OnImageContextMenuClosed">
<!-- Visual tree is different from the Window (due to the Popup).
Set CommandTarget to allow the command to traverse the visual tree
of the MainWindow to reach to the CommandBindng (defined by the MainWindow) -->
<MenuItem Header="Open Window"
Command="{x:Static local:MainWindow.ShowMyDialogCommand}"
CommandTarget="{Binding RelativeSource={RelativeSource
AncestorType=ContextMenu}, Path=PlacementTarget}" />
</ContextMenu>
</Image.ContextMenu>
</Image>
</Window>
MainWindow.xaml.cs
partial class MainWindow : Window
{
public static RoutedCommand ShowMyDialogCommand { get; } = new RoutedCommand("ShowMyDialogCommand", typeof(MainWindow));
private MainWindowViewModel MainWindowViewModel { get; }
public MainWindow()
{
InitializeComponent();
this.MainWindowViewModel = new MainWindowViewModel();
this.DataContext = this.MainWindowViewModel;
var showMyDialogCommandBinding = new CommandBinding(ShowMyDialogCommand, ExecuteShowMyDialogCommand, CanExecuteShowMyDialogCommand);
this.CommandBindings.Add(showMyDialogCommandBinding);
}
private void CanExecuteShowMyDialogCommand(object sender, CanExecuteRoutedEventArgs e)
=> e.CanExecute = true;
// Only close the ContextMenu. The ContextMenu.Closed event will continue the flow.
private void ExecuteShowMyDialogCommand(object sender, ExecutedRoutedEventArgs e)
=> (e.OriginalSource as FrameworkElement).ContextMenu.IsOpen = false;
private void OnImageContextMenuClosed(object? sender, EventArgs e)
{
// Create a semaphore that is initially blocking.
// The semaphore is used to signal the new UI thread that it must shut down
// and close the busy indicator dialog.
using var semaphore = new SemaphoreSlim(0, 1);
var uiThread = new Thread(state => ShowBusyIndicator(semaphore))
{
IsBackground = false
};
uiThread.SetApartmentState(ApartmentState.STA);
uiThread.Start();
(bool IsOk, OkDialogViewModel ViewModel) dialogResult = ShowOkDialog(semaphore);
// Do something when the user has closed the dialog e.g. using the 'OK' button
if (dialogResult.IsOk)
{
//dialogResult.ViewModel
}
}
private void ShowBusyIndicator(SemaphoreSlim semaphore)
{
// Consider to create a dedicated BusyIndicatorDialog class (following the pattern of the OkDialog).
// This allows to create a DataTemplate to design the dialog using XAML.
var busyIndicator = new Window()
{
Content = new ProgressBar() { IsIndeterminate = true },
Title = "Loading, please wait..."
};
// Let the busy indicator dialog wait for the SemaphoreSlim to signal.
// Consider to move this code directly to a dedicated BusyIndicatorDialog class.
// In case of implementing a dedicated BusyIndicatorDialog, consider to implement a special event which allows more control over the timing of the event (to replace the Loaded event).
busyIndicator.Loaded += (s, e) => OnBusyIndicatorLoaded(busyIndicator, semaphore);
busyIndicator.Show();
Dispatcher.Run();
}
// Use the Dispatcher of the busy indicator Window to post the code to the second UI thread.
private void OnBusyIndicatorLoaded(Window busyIndicator, SemaphoreSlim semaphore)
{
_ = busyIndicator.Dispatcher.InvokeAsync(async () =>
{
// Wait for the signal to continue the thread.
await semaphore.WaitAsync();
busyIndicator.Close();
busyIndicator.Dispatcher.BeginInvokeShutdown(DispatcherPriority.ContextIdle);
}, DispatcherPriority.ContextIdle);
}
private (bool IsOk, OkDialogViewModel ViewModel) ShowOkDialog(SemaphoreSlim semaphore)
{
var dialogViewModel = new OkDialogViewModel() { SomeText = "Just some text" };
var myDialog = new OkDialog()
{
Title = "I'm a Ok dialog",
DataContext = dialogViewModel
};
// Signal the busy indicator thread to continue (it will close itself and shut down the thread)
_ = semaphore.Release();
bool dialogResult = myDialog.ShowDialog() ?? false;
return (dialogResult, dialogViewModel);
}
}
MainWindow.xaml
<Grid>
<StackPanel>
<RadioButton Content="A" GroupName="ASD"
Command="{Binding ButtonCommand}"
IsChecked="True"/>
<RadioButton Content="B" GroupName="ASD"
Command="{Binding ButtonCommand}"/>
<RadioButton Content="C" GroupName="ASD"
Command="{Binding ButtonCommand}"/>
</StackPanel>
</Grid>
MainViewModel.cs
public class MainViewModel : BaseViewModel
{
private ICommand _buttonCommand;
public ICommand ButtonCommand { get { return (_buttonCommand ?? new BaseCommand(MyAction)); } }
public void MyAction()
{
Debug.WriteLine("clicked");
}
}
BaseCommand.cs
public class BaseCommand : ICommand
{
private Action _action;
public BaseCommand(Action action)
{
_action = action;
}
public event EventHandler? CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public bool CanExecute(object? parameter)
{
return true;
}
public void Execute(object? parameter)
{
_action();
}
}
When I click the button, call MyAction() and print "clicked", It works right.
When the window is loaded, I want its raise event click, call to MyAction() and print "clicked", so I set the first radiobutton IsChecked property = true, but it do not raise event. Why? And how to solve it, thanks.
Events and commands are not the same. RadioButoon has many events to reflect input events or state changes, but can invoke only a single command.
To know the available events visit the API class reference of the class e.g. by moving the cursor on the class name and then pressing "F1": RadioButton.
In your case you must handle the RadioButton.Checked event. Note that this event will be raised during the initialization routine of the RadioButton in order to fetch the local IsChecked value. If you really need wait until the button is loaded in framework terms (which means layout calculations and rendering have completed), you can defer the Checked event using the Dispatcher with a DispatcherPriority.Loaded or directly handle the RadioButton.Loaded event (instead of the Checked event alongside using the Dispatcher):
<RadioButton Content="A"
GroupName="ASD"
Checked="RadioButton_Checked"
Loaded="RadioButton_Loaded"
IsChecked="True" />
private void RadioButton_Checked(object sender, RoutedEventArgs e)
{
RadioButton radioButton = sender as RadioButton;
// Handle IsChecked changed
radionButton.Command.Execute(default);
// Alternatively, wait until the control has loaded (in case you need to reference layout related properties)
this.Dispatcher.InvokeAsync(() =>
{
radionButton.Command.Execute(default);
}, DispatcherPriority.Loaded);
// If this is a one-time operation, unregister the event handler
radioButton.Checked -= RadioButton_Checked;
}
As mentioned before, if you need the RadioButton to be completely loaded, consider to handle the RadioButton.Loaded event instead of using the Dispatcher.
Is there a way to call methods from the view from the view model? Is this good practice to do so? If not, how would I hide elements in the view from the view model? I'm just a bit confused because I'm used to working with ASP.Net, with code behind, etc.
xaml.cs
btnsave.visibility = visibility.hidden;
btnclose.visibility = visibility.hidden;
For your specific example of hiding elements in the view, you probably want to set up some properties in the ViewModel that define the conditions under which those elements are visible. Then you bind the Visibility property (with a BooleanToVisibilityConverter, most likely) of those elements in the View to those properties in the ViewModel.
More generally, you want to keep the direct coupling between them minimal if you can, but sometimes "reality" gets in the way. I've had some cases where I've passed in the View to the constructor of the ViewModel. Other cases where it's been an interface that the View implements and that gets passed into the ViewModel. So there are options. But you should make sure you HAVE to go that route before doing it.
Example:
XAML:
<Window ...>
<Window.Resources>
<BooleanToVisibilityConverter x:Key="_B2VC" />
</Window.Resources>
<StackPanel>
<Button Content="Save" Visibility="{Binding IsSaveButtonVisible}" />
<Button Content="Close" Visibility="{Binding IsCloseButtonVisible}" />
</StackPanel>
</Window>
ViewModel:
public class ViewModel: INotifyPropertyChanged
{
#region INPC Stuff
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(String propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
private bool _IsSaveButtonVisible;
public bool IsSaveButtonVisible
{
get { return _IsSaveButtonVisible; }
set
{
if (_IsSaveButtonVisible != value)
{
_IsSaveButtonVisible = value;
RaisePropertyChanged("IsSaveButtonVisible");
}
}
}
private bool _IsCloseButtonVisible;
public bool IsCloseButtonVisible
{
get { return _IsCloseButtonVisible; }
set
{
if (_IsCloseButtonVisible != value)
{
_IsCloseButtonVisible = value;
RaisePropertyChanged("IsCloseButtonVisible");
}
}
}
}
Then your ViewModel changes those properties in response to whatever it needs to (say for instance Save is only valid if they've changed something - once that something is changed, the property on the ViewModel gets updated and bam, that gets propogated to the View.
If you need further examples, i'd just suggest going and reading on MVVM. It takes a bit to grok, but its awesome once in use.
My problem is simple. I have a treeview bound to an ObservableCollection of objects, and those objects all have their own ObservableCollections. Based on user selection of other criteria on my page I want to dynamically set which checkboxes are checked. Unfortunately my checkboxes fail to update their IsChecked status after I have changed the corresponding bool Property bound to IsChecked. The checkboxes will be in the correct state the first time any node is expanded, but afterwards they stop updating. I suspect this means the objects are not created/evaluated until they are actually due to be shown for the first time.
The structure of data is Silverlight -> ViewModel -> ObservableCollection of StoreGroups LocalStoreGroups -> StoreGroup has ObservableCollection of Store Stores
Through debugging I have noticed that there are no handlers attached to this.PropertyChanged, and am wondering if this is the problem?
Treeview control :
<controls:TreeView ItemsSource="{Binding LocalStoreGroups}" ItemTemplate="{StaticResource TreeviewStoreGroupTemplate}" />
In my project I use a treeview with the following HeirarchalDataTemplates :
<UserControl.Resources>
<sdk:HierarchicalDataTemplate x:Key="TreeviewStoreTemplate">
<CheckBox IsChecked="{Binding IsSelected, Mode=TwoWay}" Content="{Binding DTO.Name}" />
</sdk:HierarchicalDataTemplate>
<sdk:HierarchicalDataTemplate x:Key="TreeviewStoreGroupTemplate" ItemsSource="{Binding Stores}" ItemTemplate="{StaticResource TreeviewStoreTemplate}">
<CheckBox IsChecked="{Binding IsSelected, Mode=TwoWay}" Content="{Binding DTO.Name}" />
</sdk:HierarchicalDataTemplate>
</UserControl.Resources>
The code for the IsSelected Property (both the StoreGroup object and the Store object have this property :
private bool _IsSelected;
public bool IsSelected
{
get { return _IsSelected; }
set
{
_IsSelected = value;
OnPropertyChanged("IsSelected");
}
}
protected void OnPropertyChanged(PropertyChangedEventArgs e)
{
PropertyChangedEventHandler temp = this.PropertyChanged;
if (null != temp)
temp(this, e);
}
Code to change IsSelected
foreach (Store s in LocalStoreGroups.SelectMany(sg => sg.Stores))
{
s.IsSelected = false;
}
foreach (StoreLink link in links)
{
Store targetStore = (from s in LocalStoreGroups.SelectMany(sg => sg.Stores) where s.DTO.ID == link.DTO.StoreID select s).FirstOrDefault();
targetStore.IsSelected = true;
}
It looks like you are updating the property in response to a load event. It is likely then that you are not on the UI thread when you update the property. Unless the change occurs on the UI thread it will not update the display.
For bound properties and properties that are collections (and not children in observable collections) it is only the OnPropertyChanged that needs to be on the UI thread. The properties can change earlier, but the UI will not change bindings until OnPropertyChanged is called.
All our ViewModels derive from a ViewModelBase we created that implements a helper SendPropertyChanged like below (so we never have to worry about cross-threading).
All our notify properties call that instead of calling OnPropertyChanged directly.
It also exposes a generally useful OnUiThread method so you can execute arbitrary code on the UI thread:
protected delegate void OnUiThreadDelegate();
public event PropertyChangedEventHandler PropertyChanged;
public void SendPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.OnUiThread(() => this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName)));
}
}
protected void OnUiThread(OnUiThreadDelegate onUiThreadDelegate)
{
if (Deployment.Current.Dispatcher.CheckAccess())
{
onUiThreadDelegate();
}
else
{
Deployment.Current.Dispatcher.BeginInvoke(onUiThreadDelegate);
}
}
Anyways, the give-away here should have been that nobody was subscribed to the PropertyChanged event. Turns out that although I implemented the PropertyChanged event I forgot to actually give the class the INotifyPropertyChanged interface.
I have simple issue setting a two-way databinding of a checkbox in Silverlight 3.0. It must be a no-brainer but probably I forgot my brain home today...
I defined a Model class to represent my .. 'data'. I implemented the INotifyPropertyChanged interface to enable the UI to see when the data changes.
public class Model : INotifyPropertyChanged
{
private bool _value;
public bool Value
{
get { return this._value; }
set
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs("Value"));
this._value = value;
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
Next I put a checkbox and a button on the .. 'form' :
<StackPanel Orientation="Horizontal">
<CheckBox x:Name="check" IsChecked="{Binding Value, Mode=TwoWay}" Content="SomeLabel"/>
<Button Click="Button_Click" Content="Test" />
</StackPanel>
Then I supplied the data in the constructor :
public MainPage()
{
InitializeComponent();
this.DataContext = new Model() { Value = true };
}
The issue is that you have to click twice on the checkbox for it to check/uncheck unless I de-implement the INotifyPropertyChanged. If de-implement it however, then the UI doesn't notice if I change the underlying data.
If I remove the Mode=TwoWay bit from the IsChecked binding expression then also the UI won't notice the underlying data change even if the Model is implementing the interface.
How can I do to :
Have the checkbox bound to the data at startup
Have the checkbox IsChecked change to modify the underlying data
Have the checkbox detect the underlying data change and update itself?
You've got a sequencing error in your set property procedure, you need to assign to _value before notifying the change :-
set
{
this._value = value;
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs("Value"));
}