C# WPF Update Status bar text and progress from another window - c#

I have a Main window named "wpfMenu" With a status bar which contains a text block and progress bar. The status bar needs to be updated from methods which are running on separate windows launched from the Main window (only one window open at any time).
Preferably I would like to pass the min, max, progress, text values to a class called "statusUpdate" to update the progress but i have no idea where to begin and any examples of updating progress bars I've come across are running on the same window.
Here is my code for the Status bar so far
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Custom="http://schemas.microsoft.com/winfx/2006/xaml/presentation/ribbon" x:Class="Mx.wpfMenu"
Title="Mx - Menu" Height="600" Width="1000" Background="#FFF0F0F0" Closed="wpfMenu_Closed">
<Grid>
<StatusBar x:Name="sbStatus" Height="26" Margin="0,0,0,0" VerticalAlignment="Bottom" HorizontalAlignment="Stretch">
<StatusBar.ItemsPanel>
<ItemsPanelTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="4*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
</Grid>
</ItemsPanelTemplate>
</StatusBar.ItemsPanel>
<StatusBarItem>
<TextBlock Name="sbMessage" Text="{Binding statusUpdate.Message}"/>
</StatusBarItem>
<StatusBarItem Grid.Column="1">
<ProgressBar Name="sbProgress" Width="130" Height="18" Minimum="0" Maximum="100" IsIndeterminate="False"/>
</StatusBarItem>
</StatusBar>
</Grid>
The code for my class is
public class statusUpdate : INotifyPropertyChanged
{
private string _message;
public event PropertyChangedEventHandler PropertyChanged;
public statusUpdate()
{
}
public statusUpdate (string value)
{
this._message = value;
}
public string Message
{
get { return _message; }
set
{
_message = value;
OnPropertyChanged("Message");
}
}
void OnPropertyChanged(string _message)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(_message));
}
}
}

There are several steps to this, but they're all well documented elsewhere. It might seem like a complex process, but it's something you'll do over and over in WPF.
You're right to store all the settings in a class. However this class needs to implement INotifyPropertyChanged and raise a PropertyChanged event in every property setter.
using System.ComponentModel;
public class StatusUpdate : INotifyPropertyChanged
{
private string message;
public event PropertyChangedEventHandler PropertyChanged;
public StatusUpdate()
{
}
public string Message
{
get { return this.message; }
set
{
this.message = value;
this.OnPropertyChanged("Message");
}
}
void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Then you can make it a public property of your code-behind class, and bind your progress bar properties to it.
public partial class MainWindow : Window
{
public MainWindow()
{
this.InitializeComponent();
}
public StatusUpdate Status { get; set; } = new StatusUpdate();
private void PlayCommand_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
}
public void PlayCommand_Executed(object sender, ExecutedRoutedEventArgs e)
{
this.Status.Message = "Play";
}
public void StopCommand_Executed(object sender, ExecutedRoutedEventArgs e)
{
this.Status.Message = "Stop";
}
}
Then you can pass a reference to the same class to the child forms, and when they set any of the properties, WPF will catch the event and update the GUI.
Let me know if you can't find an example for any of those steps.
Here's a version of your XAML with the binding and buttons I used for the above example:
<Window x:Class="WpfProgressBar.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfProgressBar"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.CommandBindings>
<CommandBinding Command="Play" Executed="PlayCommand_Executed" CanExecute="PlayCommand_CanExecute" />
<CommandBinding Command="Stop" Executed="StopCommand_Executed" />
</Window.CommandBindings>
<Grid>
<StackPanel>
<Button Content="Play" Command="Play" />
<Button Content="Stop" Command="Stop" />
</StackPanel>
<StatusBar Height="26" Margin="0,0,0,0" VerticalAlignment="Bottom" HorizontalAlignment="Stretch">
<StatusBar.ItemsPanel>
<ItemsPanelTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="4*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
</Grid>
</ItemsPanelTemplate>
</StatusBar.ItemsPanel>
<StatusBarItem>
<TextBlock Text="{Binding Status.Message, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, NotifyOnSourceUpdated=True}"/>
</StatusBarItem>
<StatusBarItem Grid.Column="1">
<ProgressBar Width="130" Height="18" Minimum="0" Maximum="100" IsIndeterminate="False"/>
</StatusBarItem>
</StatusBar>
</Grid>
</Window>
NB, I wouldn't normally do command bindings like this, but didn't want to get into the complications of adding RelayCommand

Related

Open Popup from WPF Toolbar Button inside Overflow area

I have some buttons in a toolbar. One of the buttons opens a popup. It works fine when the button is clicked while the button is shown regularly. When the button is clicked while it is in the toolbars overflow area the popup does not work. It is shown and immediately closed. Is there a solution for this?
XAML
<Window x:Class="WpfApp5.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Title="MainWindow" Height="100" Width="100"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<DockPanel LastChildFill="True">
<ToolBar DockPanel.Dock="Top">
<Button Content="First"></Button>
<Button Content="Second"></Button>
<Button Content="Third"></Button>
<Button Content="Popup" Click="OnClick"></Button>
<Popup IsOpen="{Binding IsPopupOpen, Mode=TwoWay}" StaysOpen="False">
<Border BorderBrush="Black" BorderThickness="2">
<Grid Width="150" Height="150" Background="White">
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<TextBlock Text="Popup"></TextBlock>
<Button Grid.Row="1" Content="Button In Popup" Click="PopupButtonOnClick"></Button>
</Grid>
</Border>
</Popup>
</ToolBar>
<Grid Background="White"></Grid>
</DockPanel>
</Window>
Code behind
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows;
namespace WpfApp5
{
public partial class MainWindow : Window, INotifyPropertyChanged
{
private bool _isPopupOpen;
public MainWindow()
{
InitializeComponent();
}
public bool IsPopupOpen
{
get => _isPopupOpen;
set
{
if (value == _isPopupOpen) return;
_isPopupOpen = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private void OnClick(object sender, RoutedEventArgs e)
{
IsPopupOpen = true;
}
private void PopupButtonOnClick(object sender, RoutedEventArgs e)
{
MessageBox.Show("I did it");
}
}
}

Input to and Output from User Control in WPF

I made this minimalistic project to learn output and input with user control and it's working as intended. I want to ask, is this a good approach or is there something which is not necessary?
I also want to post this, because there is tons of post with specific user cases, but not one with a simple example to learn binding mechanics.
Main Window:
<Window x:Class="OutputFromUserControl.View.OutputFromUserControlWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:OutputFromUserControl.View"
xmlns:uc="clr-namespace:OutputFromUserControl.View.Controls"
xmlns:vm="clr-namespace:OutputFromUserControl.ViewModel"
mc:Ignorable="d"
Title="Output From User Control" Height="450" Width="800">
<Window.DataContext>
<vm:MainVM x:Name="MainVM"/>
</Window.DataContext>
<StackPanel HorizontalAlignment="Left">
<Label Content="Form elements:"/>
<Border CornerRadius="5" BorderBrush="Blue" BorderThickness="1">
<Grid HorizontalAlignment="Left" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<Label Content="Name Input: " Grid.Row="0" Grid.Column="0"/>
<TextBox Grid.Row="0" Grid.Column="1"
Text="{Binding NameInput, UpdateSourceTrigger=PropertyChanged}"
Width="200"
/>
<Label Content="Surname Input: " Grid.Row="1" Grid.Column="0"/>
<TextBox Grid.Row="1" Grid.Column="1"
Text="{Binding SurnameInput, UpdateSourceTrigger=PropertyChanged}"
Width="200"
/>
<Label Content="Name Output from Control: " Grid.Row="2" Grid.Column="0"/>
<TextBlock Grid.Row="2" Grid.Column="1"
Text="{Binding FullName}"
Width="200"
/>
</Grid>
</Border>
<Label Content="User Control:" Margin="0,10,0,0"/>
<Border CornerRadius="5" BorderBrush="Red" BorderThickness="1">
<uc:NameConcatControl x:Name="NameUC"
NameInput="{Binding NameInput}"
SurnameInput="{Binding SurnameInput}"
NameOutput="{Binding FullName, Mode=TwoWay}"
/>
</Border>
</StackPanel>
</Window>
MainVM:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
namespace OutputFromUserControl.ViewModel
{
public class MainVM : INotifyPropertyChanged
{
private string nameInput;
public string NameInput {
get { return nameInput; }
set
{
nameInput = value;
OnPropertyChanged(nameof(NameInput));
}
}
private string surnameInput;
public string SurnameInput {
get { return surnameInput; }
set {
surnameInput = value;
OnPropertyChanged(nameof(SurnameInput));
}
}
private string fullName;
public string FullName {
get { return fullName; }
set {
fullName = value;
OnPropertyChanged(nameof(FullName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Control xaml:
<UserControl x:Class="OutputFromUserControl.View.Controls.NameConcatControl"
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:local="clr-namespace:OutputFromUserControl.View.Controls"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<Label Content="Name Input: " Grid.Row="0" Grid.Column="0"/>
<TextBlock Grid.Row="0" Grid.Column="1"
Text="{Binding NameInput}"
x:Name="NameInputTextBlock"
/>
<Label Content="Surname Input: " Grid.Row="1" Grid.Column="0"/>
<TextBlock Grid.Row="1" Grid.Column="1"
Text="{Binding SurnameInput}"
x:Name="SurnameInputTextBlock"
/>
<Label Content="Name Output: " Grid.Row="2" Grid.Column="0"/>
<TextBlock Grid.Row="2" Grid.Column="1"
Text="{Binding NameOutput}"
x:Name="OutputNameTextBlock"
/>
</Grid>
</UserControl>
User control .cs:
using System.Windows;
using System.Windows.Controls;
namespace OutputFromUserControl.View.Controls
{
/// <summary>
/// Interaction logic for NameConcatControl.xaml
/// </summary>
public partial class NameConcatControl : UserControl
{
public string NameInput {
get { return (string)GetValue(NameInputProperty); }
set { SetValue(NameInputProperty, value); }
}
public static string defaultNameInput = "NameInput";
public static readonly DependencyProperty NameInputProperty =
DependencyProperty.Register("NameInput", typeof(string), typeof(NameConcatControl), new PropertyMetadata(defaultNameInput, SetNameOutput));
public string SurnameInput {
get { return (string)GetValue(SurnameInputProperty); }
set { SetValue(SurnameInputProperty, value); }
}
public static string defaultSurnameInput = "Surname Input";
public static readonly DependencyProperty SurnameInputProperty =
DependencyProperty.Register("SurnameInput", typeof(string), typeof(NameConcatControl), new PropertyMetadata(defaultSurnameInput, SetNameOutput));
public string NameOutput {
get { return (string)GetValue(NameOutputProperty); }
set { SetValue(NameOutputProperty, value); }
}
public static string defaultNameOutput = "Name Output";
public static readonly DependencyProperty NameOutputProperty =
DependencyProperty.Register("NameOutput", typeof(string), typeof(NameConcatControl), new PropertyMetadata(defaultNameOutput));
private static void SetNameOutput(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
NameConcatControl control = (NameConcatControl)d;
string nameInput = "";
string surnameInput = "";
if(e.Property.Name == "NameInput")
{
string newValue = (string)e.NewValue;
nameInput = string.IsNullOrEmpty(newValue) ? "" : newValue;
}
else
{
nameInput = string.IsNullOrEmpty(control.NameInputTextBlock.Text)
? ""
: control.NameInputTextBlock.Text;
}
if(e.Property.Name == "SurnameInput")
{
string newValue = (string)e.NewValue;
surnameInput = string.IsNullOrEmpty(newValue) ? "" : newValue;
}
else
{
surnameInput = string.IsNullOrEmpty(control.SurnameInputTextBlock.Text)
? ""
: control.SurnameInputTextBlock.Text;
}
string fullName = $"{nameInput} {surnameInput}";
control.OutputNameTextBlock.Text = fullName;
control.NameOutput = fullName;
}
public NameConcatControl()
{
InitializeComponent();
}
}
}
This question has a very wide answers. Different people with different approaches can use for their applications.
But we always follow one common formula.
Each view - will have its own view model. (Again in this approach, someone might say might not be true all the time).
From your code (xaml and code), below are my observations.
<Window.DataContext>
<vm:MainVM x:Name="MainVM"/>
</Window.DataContext>
I generally don't like setting data context in xaml. Instead I prefer to set it on the code-behind (mostly from constructor)
Instead of creating a dependency properties in user control and bind the MainVM properties to the dependency properties of User control.
I prefer to do it this way.
I prefer to create a separate UserControlViewModel.cs and add required properties to it.
public class UserControlViewModel : INotifyPropertyChanged
{
private string nameInput;
public string NameInput {
get { return nameInput; }
set
{
nameInput = value;
OnPropertyChanged(nameof(NameInput));
}
}
private string surnameInput;
public string SurnameInput {
get { return surnameInput; }
set {
surnameInput = value;
OnPropertyChanged(nameof(SurnameInput));
}
}
private string fullName;
public string FullName {
get { return fullName; }
set {
fullName = value;
OnPropertyChanged(nameof(FullName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Then I prefer to add this as a property in MainVM.cs
public class MainVM : INotifyPropertyChanged
{
private UserControlViewModel _userControlViewModel;
public UserControlViewModel UserControlViewModel
{
get { return _userControlViewModel; }
set
{
_userControlViewModel = value;
OnPropertyChanged(nameof(UserControlViewModel));
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
// Rest of your code
// You don't need existing properties any more here.
// If you want to access these properties from MainVM then use the UserControlViewModel property and access the members of it.
}
Then I prefer to set the data-context of my UserControl to this property like below in my MainWindow.xaml
<uc:NameConcatControl x:Name="NameUC" ="{Binding UserControlViewModel}" />
My usercontrol contorl binding's still remain same as the property names are same and we moved to UserControlViewModel.cs
Now you can remove all dependency properties from code behind of UserControl.xaml.cs
Note :- As I stated at the beginning of my answer, this question has wide area for answers and there are lot of possibilities to answer this question.
I hope I have tried to give you some inputs from my end. I guess this should give you some idea to develop rest..
You can try making those changes and let me know in case if you face any errors or binding issues.
Assuming you just want the full-name view to be something like "Surname, Name", you could actually remove the FullName property from your view model, and just use a MultiBinding (btw the StringFormat property can be used with both MultiBindings and regular Bindings, its pretty nifty if you aren't familiar with it).
As for the Labels, it's good to make a habit of using the simplest control required to get the job done, and in this case, TextBlocks would do just fine, since you don't appear to be using any of the extended functionality the Label offers (i.e., BorderBrush, Padding, ContentTemplate, etc.).
You don't generally need to create your own dependency properties in UserControl derived classes, since they are usually designed with a particular viewmodel in mind. They are more useful when the view is independent from the viewmodel, and the dependency properties serve as an api, through which other controls/viewmodels can interact with it.
<Window x:Class="OutputFromUserControl.View.OutputFromUserControlWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:OutputFromUserControl.View"
xmlns:uc="clr-namespace:OutputFromUserControl.View.Controls"
xmlns:vm="clr-namespace:OutputFromUserControl.ViewModel"
mc:Ignorable="d"
Title="Output From User Control" Height="450" Width="800">
<Window.DataContext>
<vm:MainVM x:Name="MainVM"/>
</Window.DataContext>
<StackPanel HorizontalAlignment="Left">
<Label Content="Form elements:"/>
<Border CornerRadius="5" BorderBrush="Blue" BorderThickness="1">
<Grid HorizontalAlignment="Left" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<TextBlock Text="Name Input:" Grid.Row="0" Grid.Column="0"/>
<TextBox Grid.Row="0" Grid.Column="1"
Text="{Binding NameInput, UpdateSourceTrigger=PropertyChanged}"
Width="200"
/>
<TextBlock Text="Surname Input:" Grid.Row="1" Grid.Column="0"/>
<TextBox Grid.Row="1" Grid.Column="1"
Text="{Binding SurnameInput, UpdateSourceTrigger=PropertyChanged}"
Width="200"
/>
<TextBlock Text="Name Output from Control:" Grid.Row="2" Grid.Column="0"/>
<TextBlock Grid.Row="2" Grid.Column="1" Width="200">
<MultiBinding StringFormat="{}{0}, {1}">
<Binding Path="SurnameInput"/>
<Binding Path="NameInput"/>
</MultiBinding>
</TextBlock>
</Grid>
</Border>
<Label Content="User Control:" Margin="0,10,0,0"/>
<Border CornerRadius="5" BorderBrush="Red" BorderThickness="1">
<uc:NameConcatControl x:Name="NameUC"
NameInput="{Binding NameInput}"
SurnameInput="{Binding SurnameInput}"
NameOutput="{Binding FullName}"
/>
</Border>
</StackPanel>

Chaning view on button click inside user control

I have a two row grid inside a window. In a first row there's a stack panel with buttons. Clicking on a button shows a user control in a second row of a grid.(I got that part working). Now inside a user control there's multiple button which should change the content of a second row of a grid(change current user control to another).
When i click a button in Customers user control and put a breakpoint to NavigationCommand in BaseViewModel it actually goes there changing CurrentViewModel but does not appear in actual window.
MainWindow.xaml
<Window x:Class="TestProject.View.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewmodel="clr-namespace:TestProject.ViewModel"
xmlns:local="clr-namespace:TestProject"
xmlns:view="clr-namespace:TestProject.View"
mc:Ignorable="d"
Title="MainWindow" Width="966" Height="897">
<Window.DataContext>
<viewmodel:MainWindowViewModel/>
</Window.DataContext>
<Grid >
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<!--Верхнее меню -->
<Grid Grid.Row="0">
<StackPanel HorizontalAlignment="Left" Orientation="Horizontal" >
<Button x:Name="Visits" Background="Transparent" Command="{Binding NavigationCommand}" CommandParameter="visits" BorderThickness="0" Width="Auto" Height="Auto" Margin="8,0,0,0">
<Image Source="../icon/document-changed.png" Width="16" Height="16"/>
</Button>
<Button x:Name="Patients" Background="Transparent" Command="{Binding NavigationCommand}" CommandParameter="patients" BorderThickness="0" Width="Auto" Height="Auto" Margin="8,0,0,0">
<Image Source="../icon/document-changed.png" Width="16" Height="16"/>
</Button>
<Button x:Name="Customers" Background="Transparent" Command="{Binding NavigationCommand}" CommandParameter="customers" BorderThickness="0" Width="Auto" Height="Auto" Margin="8,0,0,0">
<Image Source="../icon/user.png" Width="16" Height="16"/>
</Button>
<Button x:Name="Goods" Background="Transparent" Command="{Binding NavigationCommand}" CommandParameter="customer" BorderThickness="0" Width="Auto" Height="Auto" Margin="8,0,0,0">
<Image Source="../icon/folder_documents.png" Width="16" Height="16"/>
</Button>
<Button x:Name="Services" Background="Transparent" Command="{Binding NavigationCommand}" CommandParameter="services" BorderThickness="0" Width="Auto" Height="Auto" Margin="8,0,0,0">
<Image Source="../icon/folder_documents.png" Width="16" Height="16"/>
</Button>
</StackPanel>
</Grid>
<ContentControl x:Name="Content" Grid.Row="1" Content="{Binding CurrentViewModel}"></ContentControl>
</Grid>
</Window>
BaseViewModel
namespace TestProject.ViewModel
{
public class BaseViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public DelegateCommand<string> NavigationCommand { get; set; }
public BaseViewModel()
{
NavigationCommand = new DelegateCommand<string>(OnNavigate);
}
private void OnNavigate(string navPath)
{
switch (navPath)
{
case "customers":
CurrentViewModel = new CustomersViewModel();
break;
case "visits":
CurrentViewModel = new VisitsViewModel();
break;
case "patients":
CurrentViewModel = new PatientsViewModel();
break;
case "customer":
CurrentViewModel = new CustomerViewModel();
break;
case "visit":
CurrentViewModel = new VisitViewModel();
break;
}
}
private object _currentViewModel;
public object CurrentViewModel
{
get { return _currentViewModel; }
set
{
if (_currentViewModel != value)
{
_currentViewModel = value;
OnPropertyChanged();
}
}
}
private object _currentText;
public object CurrentText
{
get { return _currentText; }
set
{
if (_currentText != value)
{
_currentText = value;
OnPropertyChanged();
}
}
}
}
}
Customers.xaml
<UserControl x:Class="TestProject.View.Customers"
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:local="clr-namespace:TestProject.View"
xmlns:viewModel="clr-namespace:TestProject.ViewModel"
mc:Ignorable="d"
>
<UserControl.DataContext>
<viewModel:CustomersViewModel/>
</UserControl.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="25"></RowDefinition>
<RowDefinition Height="50"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<Label Grid.Row="0" Background="#FFF3EB96">Клиенты</Label>
<StackPanel Grid.Row="1" Orientation="Horizontal">
<Button Command="{Binding NavigationCommand}" CommandParameter="customer" Background="Transparent" BorderThickness="0" Width="Auto" Height="Auto" Margin="8,0,0,0">
<StackPanel Orientation="Horizontal">
<Image Source="../icon/plus_32.png" Width="32" Height="32"/>
</StackPanel>
</Button>
</StackPanel>
</Grid>
</UserControl>
You seem to have defined the CurrentViewModel property in a common base class for all view models. This is wrong.
The ContentControl in the view binds to a specific instance of a view model class so setting the CurrentViewModel property of CustomersViewModel won't affect the ContentControl that is bound to the CurrentViewModel property of the MainWindowViewModel.
The CustomersViewModel should either have a direct reference to the MainWindowViewModel, or you will have to comminicate between them using some kind of messenger/event aggregator or shared service: https://blog.magnusmontin.net/2014/02/28/using-the-event-aggregator-pattern-to-communicate-between-view-models/.
If you inject the child view models with a reference to the MainWindowViewModel when you create them, you could use this reference to navigate:
MainWindowViewModel:
public class MainWindowViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public DelegateCommand<string> NavigationCommand { get; set; }
public BaseViewModel()
{
NavigationCommand = new DelegateCommand<string>(OnNavigate);
}
private void OnNavigate(string navPath)
{
switch (navPath)
{
case "customers":
CurrentViewModel = new CustomersViewModel(this);
break;
...
}
}
CustomersViewModel:
public class CustomersViewModel
{
private readonly MainWindowViewModel _vm;
public CustomersViewModel(MainWindowViewModel vm)
{
_vm = vm;
NavigationCommand = new DelegateCommand<string>(OnNavigate);
}
public DelegateCommand<string> NavigationCommand { get; set; }
private void OnNavigate(string navPath)
{
_vm.CurrentViewModel = new SomeOtherViewModel();
}
}

How to display user control within the main window in WPF using MVVM

I have a WPF application and just embarked in learning MVVM pattern.
My goal is that in my application the main window has a button. When this button is clicked, another window (or user control) will appear on top of the main window.
This is the code of MainWindow.xaml
<Window x:Class="SmartPole1080.View.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:utilities="clr-namespace:SoltaLabs.Avalon.Core.Utilities;assembly=SoltaLabs.Avalon.Core"
xmlns:userControls="clr-namespace:SoltaLabs.Avalon.View.Core.UserControls;assembly=SoltaLabs.Avalon.View.Core"
xmlns:controls="clr-namespace:WpfKb.Controls;assembly=SmartPole.WpfKb"
xmlns:wpf="clr-namespace:WebEye.Controls.Wpf;assembly=WebEye.Controls.Wpf.WebCameraControl"
xmlns:view="clr-namespace:SmartPole.View;assembly=SmartPole.View"
xmlns:view1="clr-namespace:SmartPole1080.View"
xmlns:behaviors="clr-namespace:SoltaLabs.Avalon.Core.Behaviors;assembly=SoltaLabs.Avalon.Core"
Title="Smart Pole"
WindowStartupLocation="CenterScreen"
Name="mainWindow"
behaviors:IdleBehavior.IsAutoReset="True" WindowState="Maximized" WindowStyle="None">
<Canvas Background="DarkGray">
<!--Main Grid-->
<Grid Width="1080" Height="1920" Background="White" Name="MainGrid"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<StackPanel Background="Black">
<Grid Background="#253341">
<Grid.RowDefinitions>
<RowDefinition Height="5"/>
<RowDefinition Height="*"/>
<RowDefinition Height="5"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="5"/>
<ColumnDefinition Width="264"/>
</Grid.ColumnDefinitions>
<Grid Grid.Row="1" Grid.Column="1">
<Button Tag="{StaticResource EmergencyImg}" Name="EmergencyButton"
Command="{Binding ShowEmergencyPanel}">
<Image Source="{StaticResource EmergencyImg}" />
</Button>
</Grid>
<!--Emergency Window Dialog-->
<Grid Name="EmergencyPanel">
<view1:EmergencyInfo x:Name="EmergencyInfoPanel"/>
</Grid>
</Grid>
</StackPanel>
</Grid>
<!--Main Grid-->
</Canvas>
This is the other window (user control - EmergencyInfo.xaml)
<UserControl x:Class="SmartPole1080.View.EmergencyInfo"
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:local="clr-namespace:SmartPole1080.View"
mc:Ignorable="d"
d:DesignHeight="1920" d:DesignWidth="1050"
x:Name="EmergencyInfoPanel">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="50"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Border Grid.Row="0" BorderBrush="White" Background="White">
<Button Background="White" BorderThickness="0" FontWeight="Bold" Foreground="Red"
HorizontalAlignment="Right" FontSize="25" Margin="0,0,15,0"
Command="{Binding HideEmergencyPanel}">
close X
</Button>
</Border>
<Image Grid.Row="1" Source="{StaticResource EdenParkInfoImg}" HorizontalAlignment="Left" />
</Grid>
I want to implement this behavior using an MVVM pattern. I have set the binding ShowEmergencyPanel in button EmergencyButton to show EmergencyInfo when this button is click.
Any help is greatly appreciated.
Why dont you make navigation, something like this. Make section for conetent that will be injected, and whatever type of object you are expecting put it in Windows.Resources in DataTemplate.
In main windo xaml
<Window.DataContext>
<local:MainWindowViewModel />
</Window.DataContext>
<Window.Resources>
<DataTemplate DataType="{x:Type home:HomeViewModel}">
<home:HomeView />
</DataTemplate>
<DataTemplate DataType="{x:Type other:OtherViewModel}">
<other:OtherView />
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid x:Name="Navigation">
<StackPanel Orientation="Horizontal">
<Button x:Name="HomeView"
Content="Home"
Margin="5"
Command="{Binding NavigationCommand}"
CommandParameter="home" />
<Button x:Name="Menu"
Content="OtherView"
Margin="5"
Command="{Binding NavigationCommand}"
CommandParameter="Other" />
</StackPanel>
</Grid>
<Grid x:Name="MainContent"
Grid.Row="1">
<ContentControl Content="{Binding CurrentViewModel}" />
</Grid>
</Grid>
MainWindowViewModel can look something like this.
public class MainWindowViewModel : INotifyPropertyChanged
{
private OtherViewModel otherVM;
private HomeViewModel homeVM;
public DelegateCommand<string> NavigationCommand { get; private set; }
public MainWindowViewModel()
{
otherVM = new OtherViewModel();
homeVM = new HomeViewModel();
// Setting default: homeViewModela.
CurrentViewModel = homeVM;
NavigationCommand = new DelegateCommand<string>(OnNavigate);
}
private void OnNavigate(string navPath)
{
switch (navPath)
{
case "other":
CurrentViewModel = otherVM;
break;
case "home":
CurrentViewModel = homeVM;
break;
}
}
private object _currentViewModel;
public object CurrentViewModel
{
get { return _currentViewModel; }
set
{
if (_currentViewModel != value)
{
_currentViewModel = value;
OnPropertyChanged();
}
}
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged = delegate { };
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged(this, new propertyChangedEventArgs(propertyName));
}
#endregion
}
Where DelegateCommand you can make yours,check how to make RelayCommand(and generic one) or use PRISM that have it's own DelegateCommand. But if you want to use PRISM, it allready has navigation to regions, that can solve many problems. Check videos from Brian Lagunas.
EDIT:
This is to show/hide grid. In your mainWindow where you set that EmergencyInfo u can show/hide that grid this way.
in your MainViewViewModel make a bool property IsVisible
private bool _isVisible;
public bool IsVisible
{
get { return _isVisible; }
set
{
_isVisible = value;
OnPropertyChanged();
}
}
in your MainView.Resources set key to BooleanToVisibilityConverter
something like:
<BooleanToVisibilityConverter x:Key="Show"/>
and your grid that you want to show/hide set visibility:
<Grid x:Name="SomeGridName"
Grid.Row="1"
Grid.Colum="1"
Visibility="{Binding IsVisible,Converter={StaticResource Show}}">
And finally set that IsVisible property to some ToggleButton, just to switch between true/false
<ToggleButton IsChecked="{Binding IsVisible}"
Content="ShowGrid" />
This way, you show/hide that grid part based on IsVisible, and you control that visibility onClick. Hope that helps.
Inside your Window xaml:
<ContentPresenter Content="{Binding Control}"></ContentPresenter>
In this way, your ViewModel must contain a Control property.
Add your ViewModel to DataContext of Window.
(For example in window constructor, this.Datacontext = new ViewModel();)
Or another way with interfaces:
public interface IWindowView
{
IUserControlKeeper ViewModel { get; set; }
}
public interface IUserControlKeeper
{
UserControl Control { get; set; }
}
public partial class YourWindow : IViewWindow
{
public YourWindow()
{
InitializeComponent();
}
public IUserControlKeeper ViewModel
{
get
{
return (IUserControlKeeper)DataContext;
}
set
{
DataContext = value;
}
}
}
(In this way, initialize your window where you want to use. Service?)
private IViewWindow _window;
private IViewWindow Window //or public...
{
get{
if(_window==null)
{
_window = new YourWindow();
_window.ViewModel = new YourViewModel();
}
return _window;
}
}
Open your window with one of your UserControls:
void ShowWindowWithControl(object ControlView, INotifyPropertyChanged ControlViewModel, bool ShowAsDialog)
{
if(ControlView is UserControl)
{ //with interface: Window.ViewModel.Control = ...
(Window.DataContext as YourViewModel).Control = (UserControl)ControlView;
if (ControlViewModel != null)
(Window.DataContext as YourViewModel).Control.DataContext = ControlViewModel;
if (ShowAsDialog) //with interface use cast to call the show...
Window.ShowDialog();
else
Window.Show();
}
}
What you can do is, place the Emergency usercontrol inside MainGrid and control its visibility through Model property.
<Canvas Background="DarkGray">
<!--Main Grid-->
<Grid Width="1080" Height="1920" Background="White" Name="MainGrid"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<StackPanel Background="Black">
<Grid Background="#253341">
<Grid.RowDefinitions>
<RowDefinition Height="5"/>
<RowDefinition Height="*"/>
<RowDefinition Height="5"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="5"/>
<ColumnDefinition Width="264"/>
</Grid.ColumnDefinitions>
<Grid Grid.Row="1" Grid.Column="1">
<Button Tag="{StaticResource EmergencyImg}" Name="EmergencyButton"
Command="{Binding ShowEmergencyPanel}">
<Image Source="{StaticResource EmergencyImg}" />
</Button>
</Grid>
</Grid>
</StackPanel>
<Grid Name="EmergencyPanel">
<view1:EmergencyInfo x:Name="EmergencyInfoPanel" Visibility={Binding IsEmergencyPanelVisible,Converter={StaticResource BoolToVisibilityConverter}} DataContext={Binding} />
</Grid>
</Grid>
<!--Main Grid-->
</Canvas>
By setting proper background to Grid that holding usercontrol, you can achieve modal window like effect.
And you need to have IsEmergencyPanelVisible(default value is false) is your Model, and this property should be changed to true on your button click command.
Note : I guess you are familiar with converters, so i included Converters in my solution.

WPF - Window doesn't close

I used the following answer to get a new window and be able to use the values in another ViewModel: https://stackoverflow.com/a/15512972/3793542
I implemented this to my application, where a user can add a new Customer. To do this, he/she clicks the button in the CustomerDetailsView, and a new window opens (CustomerAddView). Then he/she fill out the details and clicks the button in the window.
Now, the Customer is added, my ListBox updates just fine. But the window doesn't close.
I was hoping that any of you can spot what's wrong, as I can't seem to be able to figure it out and it's been driving me crazy.
The code
The OpenCloseWindowBehavior as mentioned in the linked answer is the same, only renamed to WindowBehavior.
Here's the view where the button is, CustomerDetailsView (shortened a bit):
<UserControl x:Class="QRM.View.CustomerDetailsView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:src="clr-namespace:QRM"
xmlns:view="clr-namespace:QRM.View"
xmlns:viewmodel="clr-namespace:QRM.ViewModel"
xmlns:helpers="clr-namespace:QRM.Helpers">
<UserControl.DataContext>
<viewmodel:CustomerDetailsViewModel />
</UserControl.DataContext>
<UserControl.Resources>
<ResourceDictionary Source="../StylesRD.xaml" />
</UserControl.Resources>
<i:Interaction.Behaviors>
<!-- TwoWay binding is necessary, otherwise after user closed a window directly, it cannot be opened again -->
<helpers:WindowBehavior WindowType="view:CustomerAddView" Open="{Binding AddCustomerOpen, Mode=TwoWay}" />
</i:Interaction.Behaviors>
<Grid Margin="5">
<Grid.RowDefinitions>
<RowDefinition Height="4*"/>
<RowDefinition Height="auto"/>
<RowDefinition Height="1*"/>
<RowDefinition Height="auto"/>
<RowDefinition Height="1*"/>
</Grid.RowDefinitions>
<Grid Margin="0,5,0,0">
<!-- Grid with all the details-->
</Grid>
<Line Grid.Row="1" Style="{StaticResource horizontalLineStyle}" />
<StackPanel Grid.Row="2" Orientation="Vertical">
<Button Command="{Binding AddCommand}" CommandParameter="True"> Add a new customer</Button>
<!-- More buttons-->
</StackPanel>
<Line Grid.Row="3" Style="{StaticResource horizontalLineStyle}" />
<StackPanel Grid.Row="4" Orientation="Vertical">
<!-- More buttons-->
</StackPanel>
</Grid>
</UserControl>
The new window CustomerAddView:
<Window x:Class="QRM.View.CustomerAddView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:viewmodel="clr-namespace:QRM.ViewModel"
Title="Add Customer" ResizeMode="NoResize" SizeToContent="WidthAndHeight" WindowStartupLocation="CenterScreen">
<Window.DataContext>
<viewmodel:CustomerAddViewModel />
</Window.DataContext>
<Window.Resources>
<ResourceDictionary Source="../StylesRD.xaml" />
</Window.Resources>
<Grid Margin="5">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto" />
<ColumnDefinition Width="auto" />
<ColumnDefinition Width="200" />
</Grid.ColumnDefinitions>
<!-- Form to put in new Customer's details-->
<Button Grid.Row="2" Grid.ColumnSpan="3" Margin="0,50,0,0"
Command="{Binding AddConfirmCommand}">Add this customer</Button>
</Grid>
</Window>
CustomerDetailsViewModel:
public class CustomerDetailsViewModel : INotifyPropertyChanged
{
public bool isSelected = false;
private Nullable<bool> isVisible = new Nullable<bool>();
DBCustomer dbCustomer = new DBCustomer();
#region Constructor
public CustomerDetailsViewModel()
{
Messenger messenger = App.Messenger;
messenger.Register("CustomerSelectionChanged", (Action<Customer>)(param => ProcessCustomer(param)));
}
#endregion
#region Add a Customer
private bool _addCustomerOpen;
public bool AddCustomerOpen { get { return _addCustomerOpen; } set { _addCustomerOpen = value; OnPropertyChanged("AddCustomerOpen"); } }
private RelayCommand addCommand;
public ICommand AddCommand
{
get { return addCommand ?? (addCommand = new RelayCommand(() => AddCustomer())); }
}
private void AddCustomer()
{
this.AddCustomerOpen = true;
}
//After pressing the button in AddView
private RelayCommand addDoneCommand;
public ICommand AddDoneCommand
{
get { return addDoneCommand ?? (addDoneCommand = new RelayCommand(() => AddCustomerDone(), () => !isSelected)); }
}
public void AddCustomerDone()
{
App.Messenger.NotifyColleagues("NewCustomer");
this.AddCustomerOpen = false;
}
#endregion
#region PropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (PropertyChanged != null)
PropertyChanged(this, e);
}
private void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
CustomerAddViewModel:
class CustomerAddViewModel : INotifyPropertyChanged
{
private RelayCommand addConfirmCommand;
DBCustomer dbCustomer = new DBCustomer();
public ICommand AddConfirmCommand
{
get { return addConfirmCommand ?? (addConfirmCommand = new RelayCommand(() => AddConfirmCustomer())); }
}
private void AddConfirmCustomer()
{
if (!dbCustomer.Create(newCustomer))
{
return;
}
CustomerDetailsViewModel _closeTheWindow = new CustomerDetailsViewModel();
_closeTheWindow.AddDoneCommand.Execute(false);
}
private Customer newCustomer = new Customer();
public Customer NewCustomer
{
get { return newCustomer; }
set { newCustomer = value; OnPropertyChanged(new PropertyChangedEventArgs("NewCustomer")); }
}
#region PropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (PropertyChanged != null)
PropertyChanged(this, e);
}
#endregion
}
Well you create new instance of CustomerDetailsViewModel here:
CustomerDetailsViewModel _closeTheWindow = new CustomerDetailsViewModel();
_closeTheWindow.AddDoneCommand.Execute(false);
This instance is not related to anything and it's AddCustomerOpen property is not bound to anything either, so setting it has no effect.

Categories