User usercontrol buttons in multiple views - wpf

`I am working on a WPF application (MVVM)
I have a user control(uc1) that has four buttons. cancel,accept,exit
I am going to use this control in multiple views.
I need to cancel button to revert the changes what user will make in propertygrig
user control:
<UserControl x:Class="WPF.CustomControl.RadPropertyWindowButtons"
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"
mc:Ignorable="d"
d:DesignHeight="45" d:DesignWidth="700">
<Grid>
<Grid Uid="radpropertybuttons" Height="39" VerticalAlignment="Bottom" Margin="74,0,-108,0">
<Button x:Name="Cancel"
Command="{Binding radpropertyCancel}" >
</Button>
<Button x:Name="Accept"
Command="{Binding radpropertyAccept}">
</Button>
<Button x:Name="Exit"
Command="{Binding radpropertyExit}"
CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}">
</Button>
</Grid>
</Grid>
</UserControl>
view:
<Grid Height="564" VerticalAlignment="Top" >
<Grid HorizontalAlignment="Center">
<telerik:RadLayoutControl
Name="PropertyGridContainer"
Orientation="Vertical">
</telerik:RadLayoutControl>
</Grid>
<Grid VerticalAlignment="Bottom">
<customcontrol:RadPropertyWindowButtons x:Name="ucPropertyButtons" Height="44" VerticalAlignment="Top" HorizontalAlignment="Center" Loaded="RadPropertyWindowButtons_Loaded" />
</Grid>
</Grid>
in view model
public ICommand radpropertyCancel { get; set; }
radpropertyCancel = new ViewModelCommand(execradpropertyCancel);
private void execradpropertyCancel(object obj)
{
this.RevertToOriginalData();
}
how to clear the PropertyGridContainer and bind with the data that we get from RevertToOriginalData`
I do it like this if i do from code behind if i use click event but how to do it with command.
this._viewModel.RevertToOriginalData();
this.PropertyGridContainer.Items.Clear();
this.PropertyGridContainer.Items.Add(this._viewModel.myGrid);
this.ViewModel.IsDirty = false;

this._viewModel.myGrid is wrong design if myGrid is really a Grid ( a UI element). Your view model classes must never handle UI elements or participate in/implement UI logic.
Data changes are always handled outside the view (where the data lives). Layout on the other hand is always the domain of the view.
If you want to revert the layout changes made by the user, you must do this completely in the view (code-behind).
To accomplish this, a parent control (e.g., Window) that hosts both, the RadPropertyWindowButtons and the RadLayoutControl, should expose the related commands as routed commands.
Then in the command handlers you save (serialize) the layout before edit (or alternatively on accept/after edit) and restore (deserialize) it in case the edit procedure was cancelled. The RadLayoutControl exposes a related API to help with the serialization.
Now, that the implementation of the custom control no longer depends on the explicit view model class type, the RadPropertyWindowButtons has become fully reusable in any context.
In general, to enable reusability of controls they must express their (data) dependencies as dependency properties, that are later bound to the current DataContext. The internals of the reusable control simply bind to these dependency properties (instead of binding to an explicit DataContext type). Otherwise they are only "reusable" with a particular DataContext.
MainWindow.xaml.cs
partial class MainWindow : Window
{
public static RoutedUICommand CancelEditLayoutCommand { get; } = new RoutedUICommand(
"Cancel layout edit and revert to previous state",
nameof(MainWindow.CancelEditLayoutCommand),
typeof(MainWindow));
public static RoutedUICommand AcceptLayoutCommand { get; } = new RoutedUICommand(
"Accept the current layout",
nameof(MainWindow.AcceptLayoutCommand),
typeof(MainWindow));
private Dictionary<RadLayoutControl, bool> IsInEditModeTable { get; }
private string SerializedLayout { get; set; }
public MainWindow()
{
InitializeComponent();
this.IsInEditModeTable = new Dictionary<RadLayoutControl, bool>();
var cancelEditLayoutCommandBinding = new CommandBinding(MainWindow.CancelEditLayoutCommand, ExecuteCancelEditLayoutCommand, CanExecuteCancelEditLayoutCommand);
_ = this.CommandBindings.Add(cancelEditLayoutCommandBinding);
var acceptLayoutCommandBinding = new CommandBinding(MainWindow.AcceptLayoutCommand, ExecuteAcceptLayoutCommand, CanExecuteAcceptLayoutCommand);
_ = this.CommandBindings.Add(acceptLayoutCommandBinding);
}
private void CanExecuteCancelEditLayoutCommand(object sender, CanExecuteRoutedEventArgs e)
=> e.CanExecute = e.Parameter is RadLayoutControl targetControl
&& this.IsInEditModeTable.TryGetValue(targetControl, out bool isTargetControlInEditMode)
&& isTargetControlInEditMode;
private void ExecuteCancelEditLayoutCommand(object sender, ExecutedRoutedEventArgs e)
{
var targetControl = (RadLayoutControl)e.Parameter;
RestoreLayout(targetControl);
this.IsInEditModeTable[targetControl] = false;
}
private void CanExecuteAcceptLayoutCommand(object sender, CanExecuteRoutedEventArgs e)
=> e.CanExecute = e.Parameter is RadLayoutControl targetControl
&& this.IsInEditModeTable.TryGetValue(targetControl, out bool isTargetControlInEditMode)
&& isTargetControlInEditMode;
private void ExecuteAcceptLayoutCommand(object sender, ExecutedRoutedEventArgs e)
{
var targetControl = (RadLayoutControl)e.Parameter;
SaveLayout(targetControl);
this.IsInEditModeTable[targetControl] = false;
}
// Instead of handling the SelectionChanged event I recommend
// to introduce another routed command that allows the user to put the RadLayoutControl into edit mode (by setting the RadLayoutControl.IsInEditMode accordingly).
// Aside from an improved UX this would provide a better flow or trigger to kickoff the serialization
private void OnLayoutControlSelectionChanged(object sender, LayoutControlSelectionChangedEventArgs e)
{
var targetControl = sender as RadLayoutControl;
if (this.IsInEditModeTable.TryGetValue(targetControl, out bool isTargetControlInEditMode)
&& isTargetControlInEditMode)
{
return;
}
isTargetControlInEditMode = e.NewItem is not null;
if (isTargetControlInEditMode)
{
SaveLayout(targetControl);
}
this.IsInEditModeTable[targetControl] = isTargetControlInEditMode;
}
private void SaveLayout(RadLayoutControl targetControl)
=> this.SerializedLayout = targetControl.SaveToXmlString();
private void RestoreLayout(RadLayoutControl targetControl)
=> targetControl.LoadFromXmlString(this.SerializedLayout);
}
MainWindow.xaml
<Window>
<StackPanel>
<telerik:RadLayoutControl Name="PropertyGridContainer"
IsInEditMode="True"
telerik:RadLayoutControl.SerializationId="PropertyGridContainerID"
SelectionChanged="OnLayoutControlSelectionChanged" />
<customcontrol:RadPropertyWindowButtons TargetControl="{Binding ElementName=PropertyGridContainer}" />
</StackPanel>
</Window>
RadPropertyWindowButtons.xaml.cs
class RadPropertyWindowButtons
{
public RadLayoutControl TargetControl
{
get => (RadLayoutControl)GetValue(TargetControlProperty);
set => SetValue(TargetControlProperty, value);
}
public static readonly DependencyProperty TargetControlProperty = DependencyProperty.Register(
"TargetControl",
typeof(RadLayoutControl),
typeof(RadPropertyWindowButtons),
new PropertyMetadata(default));
}
RadPropertyWindowButtons.xaml
<UserControl>
<StackPanel>
<Button x:Name="Cancel"
Command="{x:Static local:MainWindow.CancelEditLayoutCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=TargetControl}" />
<Button x:Name="Accept"
Command="{x:Static local:MainWindow.AcceptLayoutCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=TargetControl}" />
</StackPanel>
</UserControl>
See Save/Load Layout for more advanced scenarios.

Related

WPF closing context menu before open new window

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);
}
}

WPF Passing data object from Main application UI to user control

I have user controls defined to represent the contents of tab items so as to split up a large XAML file into smaller files. I would like to pass reference to a data object from the main UI class to the user controls.
I understand that DependancyProperties and RelativeSource's are ways to achieve this but am not sure how to implement this due to my lack of WPF expertise. Can someone help me.
Thanks
I have three xaml files, MainWindow, AlsTabUC (UserControl) and RangingTabUC (UserControl). I have a single object representing a device that performs both range and ambient light measurements and wojuld like to perform these activities in separate tabs.
The object m_mySensorDevice is a member of MainWindow, which is the parent and I would like to pass this object to the two children so that they can execute readAmbientLight and readRange methods.
Naturally, I have provided very basic sample code for illustration. In reality these tabs contain much more information, (along with other tabs) hence the reason for the user controls.
MainWindow - XAML
<Window.Resources>
<System:String x:Key="strTabHeaderRanging">Ranging</System:String>
<System:String x:Key="strTabHeaderALS">ALS</System:String>
</Window.Resources>
<Grid>
<TabControl Name="MainTab" TabStripPlacement="Top"
Margin="0,20,0,10"
SelectionChanged="mainTab_SelectionChanged" >
<TabItem Name="tabItemRanging"
Header="{Binding Source={StaticResource strTabHeaderRanging}}">
<Grid>
<my:rangingTabUC HorizontalAlignment="Center"
VerticalAlignment="Center"
x:Name="rangingTabUC1"/>
</Grid>
</TabItem>
<TabItem Name="tabItemAls"
Header="{Binding Source={StaticResource strTabHeaderALS}}">
<Grid>
<my:AlsTabUC HorizontalAlignment="Center"
VerticalAlignment="Center"
x:Name="alsTabUC1" />
</Grid>
</TabItem>
</TabControl>
</Grid>
</Window>
MainWindow - Code
public partial class MainWindow : Window
{
SensorDevice m_mySensorDevice;
public MainWindow()
{
m_mySensorDevice = new SensorDevice();
InitializeComponent();
}
private void mainTab_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
}
}
public class SensorDevice
{
}
AlsTabUC - XAML
<UserControl x:Class="TabUserControls.AlsTabUC">
<Grid>
<Button Height="25" Width="100" Name="readAmbientLight"
HorizontalAlignment="Center" VerticalAlignment="Center"
Click="readAmbientLight_Click" Margin="2">
Read Amb Light
</Button>
</Grid>
</UserControl>
AlsTabUC - Code
public partial class AlsTabUC : UserControl
{
public AlsTabUC()
{
InitializeComponent();
}
private void readAmbientLight_Click(object sender, RoutedEventArgs e)
{
m_mySensorDevice.readAmbientLight();
}
}
rangingTabUC- XAML
<UserControl x:Class="TabUserControls.rangingTabUC">
<Grid>
<Button Height="25" Width="100" Name="readRange"
HorizontalAlignment="Center" VerticalAlignment="Center"
Click="readRange_Click" Margin="2">
Read Range
</Button>
</Grid>
</UserControl>
rangingTabUC- Code
public partial class rangingTabUC : UserControl
{
public rangingTabUC()
{
InitializeComponent();
}
private void readRange_Click(object sender, RoutedEventArgs e)
{
m_mySensorDevice.readRange();
}
}
Since the UserControl are defined in XAML and are initialized by the code InitializeComponent of your MainWindow your are not able to use a constructor to pass a reference of your SensorDevice to the UserControls.
Add a property SensorDevice to your UserControls AlsTabUC and rangingTabUC to pass a reference of your SensorDevice to your UserControls after InitializeComponent is called in your MainWindow.
public SensorDevice Sensor {
get;
set;
}
Change the constructor of your MainWindow to the following
public MainWindow()
{
m_mySensorDevice = new SensorDevice();
InitializeComponent();
// Pass reference of SensorDevice to UserControls
rangingTabUC1.Sensor = m_mySensorDevice;
alsTabUC1.Sensor = m_mySensorDevice;
}
In your UserControls you can use the property to call the methods on your SensorDevice
SensorDevice.readAmbientLight();
or
SensorDevice.readRange();
I think you call SensorDevice's methord to get ambient or range value, so you can define your viewmodel class SensorDevice inherited from INotifyPropertyChanged interface, and define two property like Ambient or Range, and call OnPropertyChanged("Ambient"). After that, you need initialize your viewmodel in xaml , and pass it to tabcontrol's DataContext. Your usercontrol just binding to Ambient or Range property.
Code like this:
viewModel
public class SensorDevice : INotifyPropertyChanged
{
private string _ambient = string.Empty;
public string Ambient
{
get {return _ambient;}
set
{
_ambient = value;
OnPropertyChanged("Ambient");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Xaml like:
<Window.Resources>
<your_namespace_name:SensorDevice x:Key="DeviceVM" />
</Window.Resources>
<TabControl DataContext="{Binding Source={StaticResource DeviceVM}}">
<TabItem Name="tabItemRanging"
Header="{Binding Source={StaticResource strTabHeaderRanging}}">
<TextBlock Text="{Binding Path=Ambient}" />
</TabItem>
</TabControl>

Attached Behavior handling an Attached Event in WPF

I googled regarding this question but couldn't gather any information and I was wondering if it is possible for an attached behavior to handle an attached event??
I've an event declared in a class and a behavior that I am attaching to a TextBox control, the event will be raised when a button is clicked. I added the handler for this event in my behavior and wrote the logic in the event handler, but it is not executed. So, I was wondering if it is possible for an attached behavior to handle an attached event or not?
class ResetInputEventClass
{
public static readonly RoutedEvent ResetInputEvent = EventManager.RegisterRoutedEvent("ResetInput",
RoutingStrategy.Bubble,
typeof(RoutedEventHandler),
typeof(ResetInputEventClass));
public static void AddResetInputEventHandler(DependencyObject d, RoutedEventHandler handler)
{
UIElement uie = d as UIElement;
if (uie == null)
{
return;
}
uie.AddHandler(ResetInputEventClass.ResetInputEvent, handler);
}
public static void RemoveResetInputEventHandler(DependencyObject d, RoutedEventHandler handler)
{
UIElement uie = d as UIElement;
if (uie == null)
{
return;
}
uie.RemoveHandler(ResetInputEventClass.ResetInputEvent, handler);
}
}
That is my Event class and this is how I am handling it in the behavior
public class MyBehavior : Behavior<TextBoxBase>
{
public MyBehavior()
{
// Insert code required on object creation below this point.
}
protected override void OnAttached()
{
base.OnAttached();
// Insert code that you would want run when the Behavior is attached to an object.
ResetInputEventClass.AddResetInputEventHandler(AssociatedObject, OnResetInputEvent);
}
protected override void OnDetaching()
{
base.OnDetaching();
// Insert code that you would want run when the Behavior is removed from an object.
ResetInputEventClass.RemoveResetInputEventHandler(AssociatedObject, OnResetInputEvent);
}
private void OnResetInputEvent(Object o, RoutedEventArgs e)
{
//Logic
}
}
Here is my XAML Code:
<Grid x:Name="LayoutRoot">
<StackPanel>
<TextBox Margin="5" Text="Bye" TextWrapping="Wrap" Width="150">
<i:Interaction.Behaviors>
<local:MyBehavior/>
</i:Interaction.Behaviors>
</TextBox>
<TextBox Margin="5" Text="Bye" TextWrapping="Wrap" Width="150">
<i:Interaction.Behaviors>
<local:MyBehavior/>
</i:Interaction.Behaviors>
</TextBox>
<Button Name="MyButton" Content="Save" Width="50" Height="25" Click="MyButton_Click"/>
</StackPanel>
</Grid>
and I am raising the event in the click event of my button
private void MyButton_Click(object sender, RoutedEventArgs e)
{
RoutedEventArgs eventArgs = new RoutedEventArgs(ResetInputEventClass.ResetInputEvent,e.OriginalSource);
RaiseEvent(eventArgs);
}
Your problem is simple. The textbox is registered for the event, but the parent of the textbox is raising it. Thus the handler is never called. You can change the event to make it a Tunneling event instead of Bubbling. Or you can get a handle on your textbox (give it a name and reference in code behind). And have it raise the event.
<Grid x:Name="LayoutRoot">
<StackPanel>
<TextBox Margin="5" x:Name="byeTextBox" Text="Bye" TextWrapping="Wrap" Width="150">
<i:Interaction.Behaviors>
<local:MyBehavior/>
</i:Interaction.Behaviors>
</TextBox>
<Button Name="MyButton" Content="Save" Width="50" Height="25" Click="MyButton_Click"/>
</StackPanel>
</Grid>
Your code-behind should then look like this
private void MyButton_Click(object sender, RoutedEventArgs e)
{
RoutedEventArgs eventArgs = new RoutedEventArgs(ResetInputEventClass.ResetInputEvent,e.OriginalSource);
byeTextBox.RaiseEvent(eventArgs);
}
and that should fix your problem.
Of course it is possible. Show me your XAML and I ll tel you how an attached event triggers an attached behavior.
Edited:
I dont see the need why you using attached behavior and attached events because you could do everything in code behind.
Here is how to do everything in code behind:
Here is XAML without attached properties:
<Grid>
<StackPanel>
<TextBox x:Name="txtBox" Margin="5" Text="Bye" TextWrapping="Wrap" Width="150"/>
<Button Name="MyButton" Content="Save" Width="50" Height="25" Click="MyButton_Click"/>
</StackPanel>
</Grid>
This is code behind.
public MainWindow()
{
InitializeComponent();
}
private void MyButton_Click(object sender, RoutedEventArgs e)
{
this.txtBox.Text = "hello";
}
Because you have set Name property on TextBox and Button you can access them from code behind in your Window.cs and you can write your handler easly.
Here is how you can do everything with attached properties:
This is the new XAML for the solution with attached properties. I had to create my custom Interaction because the one you are using is Expression Blend or silverlight and not pure WPF.
<Grid x:Name="LayoutRoot">
<StackPanel i:Interaction.Behaviour="True">
<TextBox x:Name="txtBox" Margin="5" Text="Bye" TextWrapping="Wrap" Width="150"/>
<Button Name="MyButton" Content="Save" Width="50" Height="25" Click="MyButton_Click"/>
</StackPanel>
</Grid>
I had to set Behavior on True because the default value is false and when value is not equal to the old then the propery changed event will be called with my custom logic like this:
private void MyButton_Click(object sender, RoutedEventArgs e)
{
RoutedEventArgs eventArgs = new RoutedEventArgs(ResetInputEventClass.ResetInputEvent,e.OriginalSource);
RaiseEvent(eventArgs);
}
public class Interaction : DependencyObject
{
// Using a DependencyProperty as the backing store for Behaviour. This enables animation, styling, binding, etc...
public static readonly DependencyProperty BehaviourProperty =
DependencyProperty.RegisterAttached("Behaviour", typeof(bool), typeof(Interaction), new PropertyMetadata(false, new PropertyChangedCallback(OnBehaviourChanged)));
private static void OnBehaviourChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
StackPanel sp = (StackPanel)d;
sp.Dispatcher.BeginInvoke(new Action(() =>
{
TextBox tb = VisualTreeHelper.GetChild(sp, 0) as TextBox;
ResetInputEventClass.AddResetInputHandler(sp, new RoutedEventHandler((o, a) =>
{
// Do here whatever you want, call your custom expressions.
tb.Text = "hello";
}));
}), System.Windows.Threading.DispatcherPriority.Background);
}
}
Inside property changed event which will be called as I already mentioned when I change false to true. I wait till everything is intialized by telling the dispatcher to execute my code when application is in background. Then I find the TextBox and inject the handler which will be called when you trigger ResetInput event.
This is very complicated solution but it will work with attached events and attached properties.
I highly recommend you to use the code behind for this scenario.
Also you made a mistake inside your ResetInputEventClass class. Add and Remove methods are not correctly spelled.
This is how you should have written them:
public static void AddResetInputHandler(DependencyObject d, RoutedEventHandler handler)
{
UIElement uie = d as UIElement;
if (uie == null)
{
return;
}
uie.AddHandler(ResetInputEventClass.ResetInputEvent, handler);
}
public static void RemoveResetInputHandler(DependencyObject d, RoutedEventHandler handler)
{
UIElement uie = d as UIElement;
if (uie == null)
{
return;
}
uie.RemoveHandler(ResetInputEventClass.ResetInputEvent, handler);
}
Have fun, I hope I helped you out.
You could also have achieved this with Commands

How to update Source property of a frame by clicking only one button from user control?

I’d like to find out about how to update a source property by clicking only one "Next" button based on a click count and being able to load different pages into frame each time the button is clicked another time. Any advice is highly appreciated! Thank you in advance.
Main Window Code:
<Grid x:Name="LayoutRoot">
<Frame Content="Frame" Source="/WpfApplication1;component/Page1.xaml"/>
<local:NavUserControl HorizontalAlignment="Center" VerticalAlignment="Bottom"/>
</Grid>
User control that contains the button:
<StackPanel x:Name="LayoutRoot" Orientation="Horizontal" VerticalAlignment="Bottom" HorizontalAlignment="Center" Margin="0,0,0,20">
<Button Content="Back" HorizontalAlignment="Left" Width="75"/>
<Button Content="Next" HorizontalAlignment="Left" Width="75" />
</StackPanel>
Create a PageViewModel class that implements NextPageCommand and PreviousPageCommand commands, which raise (respectively) UserNavigatedToNextPage and UserNavigatedToPreviousPage events. To make it simple, also have them expose NextPage and PreviousPage properties of type PageViewModel. Create subclasses of PageViewModel for each page.
Create a view model class for the owning UserControl that exposes a CurrentPage property of type PageViewModel. Create all of the PageViewModel objects and set NextPage and PreviousPage on each. Add handlers for the navigation events on these object that look something like:
public void Page_UserNavigatedToNextPage(object sender, EventArgs e)
{
if (sender == CurrentPage && CurrentPage.NextPage != null)
{
CurrentPage = CurrentPage.NextPage;
}
}
Assuming that you've implemented property-change notification, now whenever the current page's NextPageCommand or PreviousPageCommand executes, the CurrentPage property will be updated and will be reflected in the UI. If you've created a data template for each page view model type, all you need is
<ContentPresenter Content="{Binding CurrentPage}"/>
in your user control and you're good to go.
If the Next/Previous buttons are in your control, and not in the page, then implement properties in the main view model that expose CurrentPage.NextPageCommand and CurrentPage.PreviousPageCommand, and bind the buttons to them.
In your NavUserControl, I would wire up either events or commands (or both, perhaps) for the next and back buttons. Then you can access those from within the MainWindow and set the appropriate value into the Source property.
If you go the event route, attach onto the events and set the Source directly.
If you go the command route, setup a command in your viewmodel, bind it to the usercontrol, and bind the Source property to another value in your viewmodel.
Edit: Adding some code per the OP's request. Keep in mind, this is not intended to be best practices. Just some examples.
To go the event route should be the simplest. You already know how to do this, I'd imagine. Just add:
public event EventHandler BackClicked;
public event EventHandler NextClicked;
private void Back_Click(object sender, RoutedEventArgs e)
{
BackClicked(sender, e);
}
private void Next_Click(object sender, RoutedEventArgs e)
{
NextClicked(sender, e);
}
events to your NavUserControl. Then change your XAML to:
<StackPanel x:Name="LayoutRoot" Orientation="Horizontal" VerticalAlignment="Bottom" HorizontalAlignment="Center" Margin="0,0,0,20">
<Button Content="Back" HorizontalAlignment="Left" Width="75" Click="Back_Click" />
<Button Content="Next" HorizontalAlignment="Left" Width="75" Click="Next_Click" />
</StackPanel>
And now in your MainWindow.xaml.cs file, add:
private void BackClicked(object sender, EventArgs e)
{
Uri source = // Whatever your business logic is to determine the previous page;
_Frame.Source = source;
}
private void NextClicked(object sender, EventArgs e)
{
Uri source = // Whatever your business logic is to determine the next page;
_Frame.Source = source;
}
and change the MainWindow XAML to be:
<Grid x:Name="LayoutRoot">
<Frame x:Name="_Frame" Content="Frame"
Source="/WpfApplication1;component/Page1.xaml"/>
<local:NavUserControl HorizontalAlignment="Center" VerticalAlignment="Bottom"
BackClicked="BackClicked" NextClicked="NextClicked" />
</Grid>
Going the command route takes a little more architecting, but is a lot more clean. I'd recommend using your favorite MVVM toolkit. My favorite is MVVMLight, so that's what I'll use for this example.
Create a ViewModel class, something like this:
public class ViewModel : GalaSoft.MvvmLight.ViewModelBase
{
private Uri _Source;
public Uri Source
{
get { return _Source; }
set
{
if (_Source != value)
{
_Source = value;
RaisePropertyChanged("Source");
}
}
}
private GalaSoft.MvvmLight.Command.RelayCommand _BackCommand;
public ICommand BackCommand
{
get
{
if (_BackCommand == null)
{
_BackCommand = new GalaSoft.MvvmLight.Command.RelayCommand(() =>
{
Uri source = // Whatever your business logic is to determine the previous page
Source = source;
});
}
return _BackCommand;
}
}
private GalaSoft.MvvmLight.Command.RelayCommand _NextCommand;
public ICommand NextCommand
{
get
{
if (_NextCommand == null)
{
_NextCommand = new GalaSoft.MvvmLight.Command.RelayCommand(() =>
{
Uri source = // Whatever your business logic is to determine the next page
Source = source;
});
}
return _NextCommand;
}
}
}
In your MainWindow.xaml.cs, create an instance of this class and set your DataContext property to that instance. Then setup your bindings:
<Grid x:Name="LayoutRoot">
<Frame Content="Frame" Source="{Binding Source}"/>
<local:NavUserControl HorizontalAlignment="Center" VerticalAlignment="Bottom"/>
</Grid>
and
<StackPanel x:Name="LayoutRoot" Orientation="Horizontal" VerticalAlignment="Bottom" HorizontalAlignment="Center" Margin="0,0,0,20">
<Button Content="Back" HorizontalAlignment="Left" Width="75" Command="{Binding BackCommand}"/>
<Button Content="Next" HorizontalAlignment="Left" Width="75" Command="{Binding NextCommand}" />
</StackPanel>
The binding example is pretty straight-forward MVVM-style WPF. I'd suggest you go that route and if you need more help, go read up on MVVM in WPF. Lots of resources out there in the form of tutorials and books. Searching here on SO can help a lot as well.
Edit again:
Change your constructor to this:
public MainWindow()
{
this.InitializeComponent();
// Insert code required on object creation below this point.
DataContext = new ViewModel();
}

WPF: Create a dialog / prompt

I need to create a Dialog / Prompt including TextBox for user input. My problem is, how to get the text after having confirmed the dialog? Usually I would make a class for this which would save the text in a property. However I want do design the Dialog using XAML. So I would somehow have to extent the XAML Code to save the content of the TextBox in a property - but I guess that's not possible with pure XAML. What would be the best way to realize what I'd like to do? How to build a dialog which can be defined from XAML but can still somehow return the input? Thanks for any hint!
The "responsible" answer would be for me to suggest building a ViewModel for the dialog and use two-way databinding on the TextBox so that the ViewModel had some "ResponseText" property or what not. This is easy enough to do but probably overkill.
The pragmatic answer would be to just give your text box an x:Name so that it becomes a member and expose the text as a property in your code behind class like so:
<!-- Incredibly simplified XAML -->
<Window x:Class="MyDialog">
<StackPanel>
<TextBlock Text="Enter some text" />
<TextBox x:Name="ResponseTextBox" />
<Button Content="OK" Click="OKButton_Click" />
</StackPanel>
</Window>
Then in your code behind...
partial class MyDialog : Window {
public MyDialog() {
InitializeComponent();
}
public string ResponseText {
get { return ResponseTextBox.Text; }
set { ResponseTextBox.Text = value; }
}
private void OKButton_Click(object sender, System.Windows.RoutedEventArgs e)
{
DialogResult = true;
}
}
Then to use it...
var dialog = new MyDialog();
if (dialog.ShowDialog() == true) {
MessageBox.Show("You said: " + dialog.ResponseText);
}
Edit: Can be installed with nuget https://www.nuget.org/packages/PromptDialog/
I just add a static method to call it like a MessageBox:
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
x:Class="utils.PromptDialog"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
WindowStartupLocation="CenterScreen"
SizeToContent="WidthAndHeight"
MinWidth="300"
MinHeight="100"
WindowStyle="SingleBorderWindow"
ResizeMode="CanMinimize">
<StackPanel Margin="5">
<TextBlock Name="txtQuestion" Margin="5"/>
<TextBox Name="txtResponse" Margin="5"/>
<PasswordBox Name="txtPasswordResponse" />
<StackPanel Orientation="Horizontal" Margin="5" HorizontalAlignment="Right">
<Button Content="_Ok" IsDefault="True" Margin="5" Name="btnOk" Click="btnOk_Click" />
<Button Content="_Cancel" IsCancel="True" Margin="5" Name="btnCancel" Click="btnCancel_Click" />
</StackPanel>
</StackPanel>
</Window>
And the code behind:
public partial class PromptDialog : Window
{
public enum InputType
{
Text,
Password
}
private InputType _inputType = InputType.Text;
public PromptDialog(string question, string title, string defaultValue = "", InputType inputType = InputType.Text)
{
InitializeComponent();
this.Loaded += new RoutedEventHandler(PromptDialog_Loaded);
txtQuestion.Text = question;
Title = title;
txtResponse.Text = defaultValue;
_inputType = inputType;
if (_inputType == InputType.Password)
txtResponse.Visibility = Visibility.Collapsed;
else
txtPasswordResponse.Visibility = Visibility.Collapsed;
}
void PromptDialog_Loaded(object sender, RoutedEventArgs e)
{
if (_inputType == InputType.Password)
txtPasswordResponse.Focus();
else
txtResponse.Focus();
}
public static string Prompt(string question, string title, string defaultValue = "", InputType inputType = InputType.Text)
{
PromptDialog inst = new PromptDialog(question, title, defaultValue, inputType);
inst.ShowDialog();
if (inst.DialogResult == true)
return inst.ResponseText;
return null;
}
public string ResponseText
{
get
{
if (_inputType == InputType.Password)
return txtPasswordResponse.Password;
else
return txtResponse.Text;
}
}
private void btnOk_Click(object sender, RoutedEventArgs e)
{
DialogResult = true;
Close();
}
private void btnCancel_Click(object sender, RoutedEventArgs e)
{
Close();
}
}
So you can call it like:
string repeatPassword = PromptDialog.Prompt("Repeat password", "Password confirm", inputType: PromptDialog.InputType.Password);
Great answer of Josh, all credit to him, I slightly modified it to this however:
MyDialog Xaml
<StackPanel Margin="5,5,5,5">
<TextBlock Name="TitleTextBox" Margin="0,0,0,10" />
<TextBox Name="InputTextBox" Padding="3,3,3,3" />
<Grid Margin="0,10,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Button Name="BtnOk" Content="OK" Grid.Column="0" Margin="0,0,5,0" Padding="8" Click="BtnOk_Click" />
<Button Name="BtnCancel" Content="Cancel" Grid.Column="1" Margin="5,0,0,0" Padding="8" Click="BtnCancel_Click" />
</Grid>
</StackPanel>
MyDialog Code Behind
public MyDialog()
{
InitializeComponent();
}
public MyDialog(string title,string input)
{
InitializeComponent();
TitleText = title;
InputText = input;
}
public string TitleText
{
get { return TitleTextBox.Text; }
set { TitleTextBox.Text = value; }
}
public string InputText
{
get { return InputTextBox.Text; }
set { InputTextBox.Text = value; }
}
public bool Canceled { get; set; }
private void BtnCancel_Click(object sender, System.Windows.RoutedEventArgs e)
{
Canceled = true;
Close();
}
private void BtnOk_Click(object sender, System.Windows.RoutedEventArgs e)
{
Canceled = false;
Close();
}
And call it somewhere else
var dialog = new MyDialog("test", "hello");
dialog.Show();
dialog.Closing += (sender,e) =>
{
var d = sender as MyDialog;
if(!d.Canceled)
MessageBox.Show(d.InputText);
}
You don't need ANY of these other fancy answers. Below is a simplistic example that doesn't have all the Margin, Height, Width properties set in the XAML, but should be enough to show how to get this done at a basic level.
XAML
Build a Window page like you would normally and add your fields to it, say a Label and TextBox control inside a StackPanel:
<StackPanel Orientation="Horizontal">
<Label Name="lblUser" Content="User Name:" />
<TextBox Name="txtUser" />
</StackPanel>
Then create a standard Button for Submission ("OK" or "Submit") and a "Cancel" button if you like:
<StackPanel Orientation="Horizontal">
<Button Name="btnSubmit" Click="btnSubmit_Click" Content="Submit" />
<Button Name="btnCancel" Click="btnCancel_Click" Content="Cancel" />
</StackPanel>
Code-Behind
You'll add the Click event handler functions in the code-behind, but when you go there, first, declare a public variable where you will store your textbox value:
public static string strUserName = String.Empty;
Then, for the event handler functions (right-click the Click function on the button XAML, select "Go To Definition", it will create it for you), you need a check to see if your box is empty. You store it in your variable if it is not, and close your window:
private void btnSubmit_Click(object sender, RoutedEventArgs e)
{
if (!String.IsNullOrEmpty(txtUser.Text))
{
strUserName = txtUser.Text;
this.Close();
}
else
MessageBox.Show("Must provide a user name in the textbox.");
}
Calling It From Another Page
You're thinking, if I close my window with that this.Close() up there, my value is gone, right? NO!! I found this out from another site: http://www.dreamincode.net/forums/topic/359208-wpf-how-to-make-simple-popup-window-for-input/
They had a similar example to this (I cleaned it up a bit) of how to open your Window from another and retrieve the values:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void btnOpenPopup_Click(object sender, RoutedEventArgs e)
{
MyPopupWindow popup = new MyPopupWindow(); // this is the class of your other page
//ShowDialog means you can't focus the parent window, only the popup
popup.ShowDialog(); //execution will block here in this method until the popup closes
string result = popup.strUserName;
UserNameTextBlock.Text = result; // should show what was input on the other page
}
}
Cancel Button
You're thinking, well what about that Cancel button, though? So we just add another public variable back in our pop-up window code-behind:
public static bool cancelled = false;
And let's include our btnCancel_Click event handler, and make one change to btnSubmit_Click:
private void btnCancel_Click(object sender, RoutedEventArgs e)
{
cancelled = true;
strUserName = String.Empty;
this.Close();
}
private void btnSubmit_Click(object sender, RoutedEventArgs e)
{
if (!String.IsNullOrEmpty(txtUser.Text))
{
strUserName = txtUser.Text;
cancelled = false; // <-- I add this in here, just in case
this.Close();
}
else
MessageBox.Show("Must provide a user name in the textbox.");
}
And then we just read that variable in our MainWindow btnOpenPopup_Click event:
private void btnOpenPopup_Click(object sender, RoutedEventArgs e)
{
MyPopupWindow popup = new MyPopupWindow(); // this is the class of your other page
//ShowDialog means you can't focus the parent window, only the popup
popup.ShowDialog(); //execution will block here in this method until the popup closes
// **Here we find out if we cancelled or not**
if (popup.cancelled == true)
return;
else
{
string result = popup.strUserName;
UserNameTextBlock.Text = result; // should show what was input on the other page
}
}
Long response, but I wanted to show how easy this is using public static variables. No DialogResult, no returning values, nothing. Just open the window, store your values with the button events in the pop-up window, then retrieve them afterwards in the main window function.

Resources