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();
}
}
Related
I cannot get a change in the UI (TextBox) when selecting a tree item. When I select a tree item, the property and the text should change. But this does not happen. Property changes, but text remains the same. At first I tried to cause a property change in MainViewModel, but ran into the same problem: the property changes but the UI remains unchanged.
public class BindableSelectedItemBehavior : Behavior<TreeView>, INotifyPropertyChanged
{
MainViewModel vm = new MainViewModel();
private string _message;
public string Message
{
get
{
return _message;
}
set
{
_message = value;
OnPropertyChanged("Message");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public object SelectedItem
{
get { return GetValue(SelectedItemProperty); }
set { SetValue(SelectedItemProperty, value); }
}
public static readonly DependencyProperty SelectedItemProperty =
DependencyProperty.Register(nameof(SelectedItem), typeof(object), typeof(BindableSelectedItemBehavior), new UIPropertyMetadata(null, OnSelectedItemChanged));
private static void OnSelectedItemChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var item = e.NewValue as TreeViewItem;
if (item != null)
{
item.SetValue(TreeViewItem.IsSelectedProperty, true);
}
}
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.SelectedItemChanged += OnTreeViewSelectedItemChanged;
}
protected override void OnDetaching()
{
base.OnDetaching();
if (AssociatedObject != null)
{
AssociatedObject.SelectedItemChanged -= OnTreeViewSelectedItemChanged;
}
}
private void OnTreeViewSelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
SelectedItem = e.NewValue;
string name = GetName((Item)SelectedItem);
Message = "edgddgdgdg";
}
public string GetName(Item item)
{
string circ = item.Name;
return circ;
}
}
}
<UserControl x:Class="WpfApp3.TreeViewTextChange"
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:WpfApp3"
xmlns:vm="clr-namespace:WpfApp3.ViewModel"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.DataContext>
<vm:BindableSelectedItemBehavior/>
</UserControl.DataContext>
<Grid>
<TextBox Grid.Row="1" Grid.Column="2" Text="{Binding Path=Message, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Background="Aqua" Width="200" Height="50"/>
</Grid>
</UserControl>
Used a debugger - property changes but does not change textbox in UserControl.
My MainViewModel
class MainViewModel : BindableBase
{
MainModel mainModel = new MainModel();
private ICollectionView root;
public MainViewModel()
{
OpenSth = new DelegateCommand(DisplayItemTree);
ShowCheck = new DelegateCommand(ShowCheckedItemsCommand);
Sth = new DelegateCommand(Changetext);
TextChengedCommand = new DelegateCommand<object>(textChange);
}
public ICommand OpenSth { get; }
public ICommand ShowCheck { get; }
public ICommand Sth { get; }
public ICommand TextChengedCommand { get; }
private void textChange(object obj)
{
var str = obj as string;
}
public void DisplayItemTree()
{
var itemTree = mainModel.GetItemsTree();
Root = CollectionViewSource.GetDefaultView(itemTree);
Root.Filter = new Predicate<object>((item) =>
{
var realItem = (Item)item;
return true;
});
Root.Refresh();
}
public ICollectionView Root
{
get => root;
set => SetProperty(ref root, value);
}
public void ShowCheckedItemsCommand()
{
var str = new StringBuilder();
List<Item> items = mainModel.FindCheckedItem();
if (items.Count == 0)
{
str.Append("No selected items");
}
else
{
foreach (var item in items)
{
str.Append(item.Name);
}
}
MessageBox.Show(str.ToString());
}
private string _name;
public string TextText
{
get { return _name; }
set
{
_name = value;
RaisePropertyChanged("TextText");
}
}
public void Changetext()
{
TextText = "dggg";
}
}
}
And xaml code
<Window x:Class="WpfApp3.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:WpfApp3"
xmlns:beh="clr-namespace:WpfApp3.ViewModel"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:ii="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:prism="http://www.codeplex.com/prism"
mc:Ignorable="d"
xmlns:vm="clr-namespace:WpfApp3.ViewModel"
xmlns:bh="clr-namespace:WpfApp3.ViewModel"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<vm:MainViewModel/>
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50"/>
<ColumnDefinition Width="200"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<DockPanel Grid.Column="1" Grid.Row="0" Grid.RowSpan="2">
<TextBox x:Name="searchBox" DockPanel.Dock="Top" Height="30" VerticalAlignment="Top" BorderBrush="Black" BorderThickness="1" Text="{Binding SearchText}">
<ii:Interaction.Triggers>
<ii:EventTrigger EventName="TextChenged">
<ii:InvokeCommandAction Command="{Binding TextChengedCommand}" CommandParameter="{Binding ElementName=searchBox, Path=Text}"/>
</ii:EventTrigger>
</ii:Interaction.Triggers>
</TextBox>
<DockPanel DockPanel.Dock="Bottom">
<Button Content="Get TreeView" Command="{Binding OpenSth}" Width="100"/>
<Button Content="Show Check Item" Command="{Binding ShowCheck}" Width="100"/>
</DockPanel>
<TreeView BorderBrush="Black" BorderThickness="1" ItemsSource="{Binding Root}">
<i:Interaction.Behaviors>
<beh:BindableSelectedItemBehavior SelectedItem="{Binding SelectedNode, Mode=TwoWay}"/>
</i:Interaction.Behaviors>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Sub}">
<DockPanel>
<CheckBox IsChecked="{Binding CheckedItem}" Margin="0 8 0 8" VerticalAlignment="Center" DockPanel.Dock="Left"/>
<TextBlock Text="{Binding Name}" VerticalAlignment="Center" DockPanel.Dock="Right"/>
</DockPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</DockPanel>
<DataGrid AutoGenerateColumns="False" Height="210" HorizontalContentAlignment="Left" Name="DataGrid" VerticalAlignment="Top" Grid.Column="2" Grid.Row="0" Margin="0,0,0,0" ItemsSource="{Binding ListOfCircuets}">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Length}" MinWidth="50"/>
<DataGridTextColumn Binding="{Binding dU}" MinWidth="50"/>
<DataGridTextColumn Binding="{Binding Cable}" MinWidth="50"/>
<DataGridTextColumn Binding="{Binding I}" MinWidth="50"/>
</DataGrid.Columns>
</DataGrid>
<TextBox Grid.Row="1" Grid.Column="2" Text="{Binding TextText, Mode=TwoWay}" Background="Aqua" Width="200" Height="50"/>
<Button Content="Show Check Item" Command="{Binding Sth}" Width="100"/>
<local:TreeViewTextChange Grid.Row="1" Grid.Column="2"></local:TreeViewTextChange>
</Grid>
</Window>
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
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.
I've created an example based on MVVM
Main window XAML:
<Window x:Class="LearnMVVM.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:LearnMVVM"
xmlns:System="clr-namespace:System;assembly=mscorlib"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:ViewModel />
</Window.DataContext>
<Window.Resources>
<ObjectDataProvider x:Key="operationTypeEnum" MethodName="GetValues" ObjectType="{x:Type System:Enum}">
<ObjectDataProvider.MethodParameters>
<x:Type TypeName="local:OperationType"/>
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
<DataTemplate DataType="{x:Type local:SomeUserControlViewModel}">
<local:SomeUserControl />
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="25"/>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="25"/>
<RowDefinition Height="25"/>
<RowDefinition />
</Grid.RowDefinitions>
<TextBox Grid.Column="0" Grid.Row="0" Margin="2" Text="{Binding Path=A, Mode=TwoWay}"/>
<TextBlock Grid.Column="1" Grid.Row="0" Text="+" TextAlignment="Center" VerticalAlignment="Center" Height="16" Margin="0,4,0,5"/>
<TextBox Grid.Column="2" Grid.Row="0" Margin="2" Text="{Binding Path=B, Mode=TwoWay}"/>
<Button Grid.Column="3" Grid.Row="0" Margin="2" Content="Посчитать" Command="{Binding ClickCommand}"/>
<TextBox Grid.Column="4" Grid.Row="0" Margin="2" IsReadOnly="True" Text="{Binding Path=Summa, Mode=TwoWay}"/>
<ComboBox Grid.Column="2" Grid.Row="1" SelectedItem="{Binding Path=SomeUserControl.Operation, Mode=TwoWay}" ItemsSource="{Binding Source={StaticResource operationTypeEnum}}" />
<ContentControl Grid.Column="2" Grid.Row="2" BorderThickness="3" BorderBrush="Black" Content="{Binding Path=SomeUserControl}" />
</Grid>
</Window>
XAML of the SomeUserControl:
<UserControl x:Class="LearnMVVM.SomeUserControl"
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:learnMvvm="clr-namespace:LearnMVVM"
xmlns:diag="clr-namespace:System.Diagnostics;assembly=WindowsBase"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.DataContext>
<learnMvvm:SomeUserControlViewModel />
</UserControl.DataContext>
<DockPanel>
<TextBox DockPanel.Dock="Top" Margin="10" Text="{Binding Path=A, Mode=TwoWay, diag:PresentationTraceSources.TraceLevel=High}" />
<Label DockPanel.Dock="Top" Content="{Binding Path=Operation, diag:PresentationTraceSources.TraceLevel=High}" />
<TextBox DockPanel.Dock="Top" Margin="10" Text="{Binding Path=B, Mode=TwoWay, diag:PresentationTraceSources.TraceLevel=High}" />
<Button DockPanel.Dock="Top" Content="=" Margin="20" Command="{Binding CalculateOperationComamnd, Mode=TwoWay, diag:PresentationTraceSources.TraceLevel=High}" />
<Label DockPanel.Dock="Top" Margin="10" Content="{Binding Path=Result, diag:PresentationTraceSources.TraceLevel=High}" />
</DockPanel>
</UserControl>
ViewModel of the SomeCustomUserControl:
using System;
using System.ComponentModel;
using System.Windows.Input;
namespace LearnMVVM
{
public enum OperationType
{
Sum,
Sub,
Div,
Mul
}
public class SomeUserControlViewModel : INotifyPropertyChanged
{
public double A { get; set; }
public double B { get; set; }
//Команды
private ICommand calculateOperationCommand;
public ICommand CalculateOperationComamnd
{
get
{
return calculateOperationCommand;
}
set
{
if (calculateOperationCommand != value)
{
calculateOperationCommand = value;
OnPropertyChanged("CalculateOperationComamnd");
}
}
}
private OperationType operation;
public OperationType Operation
{
get
{
return operation;
}
set
{
if (operation != value)
{
operation = value;
switch (operation)
{
case OperationType.Sum:
CalculateOperationComamnd = new RelayCommand(arg => OperationSum());
break;
case OperationType.Sub:
CalculateOperationComamnd = new RelayCommand(arg => OperationSub());
break;
case OperationType.Div:
CalculateOperationComamnd = new RelayCommand(arg => OperationDiv());
break;
case OperationType.Mul:
CalculateOperationComamnd = new RelayCommand(arg => OperationMul());
break;
}
OnPropertyChanged("Operation");
}
}
}
private void OperationSum()
{
Result = A + B;
}
private void OperationSub()
{
Result = A - B;
}
private void OperationDiv()
{
Result = A/B;
}
private void OperationMul()
{
Result = A*B;
}
private double result;
public double Result
{
get { return result; }
set
{
if (Math.Abs(result - value) > 0.0001)
{
result = value;
OnPropertyChanged("Result");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
Problem: the custom control does not change, when i'm changing selected item from the combo box and the "calculate button has no effect.
Actually all properties within of SomeCustomControlViewModel are updated as expected, but there is no effect in the main windows.
Have i missed something?
Operation is not a property of SomeUserControl. It is a property of SomeUserControl's viewmodel -- reachable as the control's DataContext. Try binding ComboBox.SelectedItem like so:
SelectedItem="{Binding Path=SomeUserControl.DataContext.Operation, Mode=TwoWay}"
The change is that I added DataContext to the path.
This is why you don't use viewmodels with custom controls, if you really want to use them as controls. You write a control class deriving from Control, and give it dependency properties. Operation should be a dependency property of a class derived from Control, not a notifying property on a viewmodel. Then you define UI for it by applying a ControlTemplate via a default Style.
What you've got here is really a child viewmodel. With that type of arrangement, ordinarily the parent viewmodel would provide an instance of the child viewmodel, and bind it to the child control itself. Then anybody who wanted to use a property of the child viewmodel would bind ChildVM.WhateverProperty.
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.