I've spent the last days reading and trying to apply the Navigation pattern from this page: https://rachel53461.wordpress.com/2011/12/18/navigation-with-mvvm-2/
Now, after I got my project to work I'm really confused about how the binding works here. At first I have to clarify that I don't want a Navigation pane which is always visible like in the given example. I just want to use my MainView for navigation and each "SubView" should be able to go back to it's "parent" only.
Here's what I've got:
Project: APP
Class: App.xaml.cs
protected override void OnStartup(StartupEventArgs e) {
base.OnStartup(e);
UI.View.Main.MainView app = new UI.View.Main.MainView();
UI.View.Main.MainViewModel viewModel = new UI.View.Main.MainViewModel(some dependencies);
app.DataContext = viewModel;
app.Show();
}
ViewModel Base Class
public abstract class BaseViewModel : INotifyPropertyChanged {
public event PropertyChangedEventHandler PropertyChanged;
private string _name;
public string Name {
get {
return _name;
}
set {
if (Name != value) {
_name = value;
OnPropertyChanged("Name");
}
}
}
private BaseViewModel _homePage;
public BaseViewModel HomePage {
get {
return _homePage;
}
set {
if (HomePage != value) {
_homePage = value;
OnPropertyChanged("HomePage");
}
}
}
public void OnPropertyChanged(string propertyName) {
PropertyChangedEventHandler temp = PropertyChanged;
if (temp != null) {
temp(this, new PropertyChangedEventArgs(propertyName));
}
}
}
MainViewModel
namespace SGDB.UI.View.Main {
public class MainViewModel : BaseViewModel {
private BaseViewModel _currentPageViewModel;
public BaseViewModel CurrentPageViewModel {
get {
return _currentPageViewModel;
}
set {
if (CurrentPageViewModel != value) {
_currentPageViewModel = value;
OnPropertyChanged("CurrentPageViewModel");
}
}
}
public List<BaseViewModel> PageViewModels { get; private set; }
public RelayCommand ChangePageCommand {
get {
return new RelayCommand(p => ChangeViewModel((BaseViewModel)p), p => p is BaseViewModel);
}
}
//Some Dependencies
public List<BaseViewModel> ViewPages { get; private set; }
public MainViewModel(some dependencies) {
HomePage = new HomeViewModel() { Name = "TEST" };
//assign dependencies
var uavm = new UserAdministration.UserAdministrationViewModel(_userUnitOfWork, _personUnitOfWork) {
Name = Resources.Language.Sys.UserAdministartionTitle
};
PageViewModels = new List<BaseViewModel>();
PageViewModels.Add(uavm);
ChangeViewModel(HomePage);
}
public void ChangeViewModel(BaseViewModel viewModel) {
CurrentPageViewModel = viewModel;
}
}
}
MainView
<Window x:Class="SGDB.UI.View.Main.MainView"
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:SGDB.UI.View.Main"
xmlns:ua="clr-namespace:SGDB.UI.View.UserAdministration"
xmlns:home="clr-namespace:SGDB.UI.View.Home"
mc:Ignorable="d"
Title="MainView" Height="400" Width="800">
<Window.Resources>
<DataTemplate DataType="{x:Type home:HomeViewModel}">
<home:Home/>
</DataTemplate>
<DataTemplate DataType="{x:Type ua:UserAdministrationViewModel}">
<ua:UserAdministration/>
</DataTemplate>
</Window.Resources>
<ContentControl Content="{Binding CurrentPageViewModel}"/>
HomeViewModel
public class HomeViewModel : BaseViewModel {
public RelayCommand TestCommand {
get {
return new RelayCommand((x) => MessageBox.Show(x.ToString()), (x) => true);
}
}
}
HomeView
<UserControl x:Class="SGDB.UI.View.Home.Home"
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:SGDB.UI.View.Home"
xmlns:controls="clr-namespace:SGDB.UI.Controls"
xmlns:resx="clr-namespace:SGDB.UI.Resources.Language"
mc:Ignorable="d"
d:DesignHeight="400" d:DesignWidth="800">
<Grid>
<Grid.Resources>
<Style TargetType="controls:ModernButton">
<Setter Property="Margin" Value="1"/>
<Setter Property="FontFamily" Value="Bosch Office Sans"/>
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="Size" Value="155"/>
</Style>
</Grid.Resources>
<Grid.Background>
<LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
<LinearGradientBrush.GradientStops>
<GradientStop Color="#26688B" Offset="1"/>
<GradientStop Color="#11354C" Offset="0"/>
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Grid.Background>
<Grid.RowDefinitions>
<RowDefinition Height="60"/>
<RowDefinition Height="3*"/>
<RowDefinition Height="2*"/>
<RowDefinition Height="25"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="150"/>
</Grid.ColumnDefinitions>
<StackPanel Grid.Row="0" Grid.Column="0" HorizontalAlignment="Center">
<StackPanel.Resources>
<Style TargetType="TextBlock">
<Setter Property="Foreground" Value="White"/>
<Setter Property="FontFamily" Value="Bosch Office Sans"/>
</Style>
</StackPanel.Resources>
<TextBlock Text="{x:Static resx:Sys.ApplicationTitle}" FontSize="20" FontWeight="Bold" Margin="5"/>
<TextBlock Text="{x:Static resx:Sys.ApplicationSubTitle}" FontSize="12" FontWeight="Light"/>
</StackPanel>
<WrapPanel Grid.Row="1"
Grid.Column="0"
FlowDirection="LeftToRight"
HorizontalAlignment="Left"
Width="367">
<ItemsControl ItemsSource="{Binding DataContext.PageViewModels, RelativeSource={RelativeSource AncestorType={x:Type Window}}}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<controls:ModernButton Background="Dark"
Text="{Binding Name}"
Command="{Binding DataContext.ChangePageCommand, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
CommandParameter="{Binding}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Button Content="{Binding Name}" Command="{Binding DataContext.ChangePageCommand, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
CommandParameter="{Binding HomePage}"/>
// This Button is always disabled although HomePage is of Type HomeViewModel which is based on BaseViewModel.
</WrapPanel>
</Grid>
My questions are:
Why does the HomeView knot that the HomeViewModel is it's ViewModel? I do not define it anywhere in my code.
Why does the Binding on the Name Property work but binding to the HomePage Property doesn't? Both of them are defined in the BaseViewModel class.
Update 1:
RelayCommand class:
public class RelayCommand : ICommand {
public event EventHandler CanExecuteChanged;
readonly Action<object> _action;
readonly Predicate<object> _predicate;
public RelayCommand(Action<object> action, Predicate<object> predicate) {
_action = action;
_predicate = predicate;
}
public RelayCommand(Action<object> action) {
_action = action;
_predicate = ((x) => true);
}
public bool CanExecute(object parameter) {
return _predicate(parameter);
}
public void Execute(object parameter) {
_action(parameter);
}
}
Update 2:
What's the actual problem?
<Button Content="{Binding Name}" Command="{Binding DataContext.ChangePageCommand, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
CommandParameter="{Binding HomePage}"/>
The Content gets bound properly but the CommandParameter (HomePage) which should be of Type BaseViewModel won't get validated through the Command's CanExecute. Both the Properties, Name and HomePage are defined inside the BaseViewModel.
Update 3:
<Button Content="{Binding Name}" Command="{Binding DataContext.ChangePageCommand, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
CommandParameter="{Binding DataContext.HomePage, ElementName=Test}"/>
In your there is the next lines:
<DataTemplate DataType="{x:Type home:HomeViewModel}">
<home:Home/>
</DataTemplate>
meaning that the visual form of HomeViewModel is Home.
Your Binding works fine, I think your problem is the command itself. I don't know what is RelayCommand but i think your bug is from there.
RelayCommand should be something like this:
public abstract class BaseViewModel : INotifyPropertyChanged
{
private ICommand _f1KeyCommand;
public ICommand F1KeyCommand
{
get
{
if (_f1KeyCommand == null)
_f1KeyCommand = new DelegateCommand(F1KeyCommandCallback, CanExecute);
return _f1KeyCommand;
}
}
/// <summary>
/// Fired if F1 is pressend and 'CanExecute' returns true
/// </summary>
private void F1KeyCommandCallback(object obj)
{
Console.WriteLine("F1KeyCommandCallback fired");
}
// ....
}
This class allows delegating the commanding logic to methods passed as parameters,and enables a View to bind commands to objects that are not part of the element tree:
public class DelegateCommand : ICommand
{
#region Data Members
private Action<object> execute;
private Predicate<object> canExecute;
private event EventHandler CanExecuteChangedInternal;
#endregion
#region Ctor
public DelegateCommand(Action<object> execute)
: this(execute, DefaultCanExecute)
{
}
public DelegateCommand(Action<object> execute, Predicate<object> canExecute)
{
if (execute == null)
{
throw new ArgumentNullException("execute");
}
if (canExecute == null)
{
throw new ArgumentNullException("canExecute");
}
this.execute = execute;
this.canExecute = canExecute;
}
#endregion
#region Properties
public event EventHandler CanExecuteChanged
{
add
{
CommandManager.RequerySuggested += value;
this.CanExecuteChangedInternal += value;
}
remove
{
CommandManager.RequerySuggested -= value;
this.CanExecuteChangedInternal -= value;
}
}
#endregion
#region Public Methods
public bool CanExecute(object parameter)
{
return this.canExecute != null && this.canExecute(parameter);
}
public void Execute(object parameter)
{
this.execute(parameter);
}
public void OnCanExecuteChanged()
{
EventHandler handler = this.CanExecuteChangedInternal;
if (handler != null)
{
handler.Invoke(this, EventArgs.Empty);
}
}
public void Destroy()
{
this.canExecute = _ => false;
this.execute = _ => { return; };
}
#endregion
#region Private Methods
private static bool DefaultCanExecute(object parameter)
{
return true;
}
#endregion
}
In your view:
<controls:ModernButton Background="Dark"
Text="{Binding Name}"
Command="{Binding F1KeyCommand"
CommandParameter="{Binding}"/>
Related
When I click on a row of WPF datagrid, I want to open a new Window that has information about a person from the clicked row (that can be changed) by using binding. How can I do it? And how can I save the changed information?
Thanks in Advance!
I did solve a simelar propblem using the Behaviours Libary.
First Make sure you have the Nuget package: "Microsoft.Xaml.Behaviors.Wpf" installed.
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
<DataGrid>
<i:Interaction.Triggers>
<i:EventTrigger
EventName="MouseUp">
<i:InvokeCommandAction
Command="{Binding OpenWindowCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=DataGrid}, Path=SelectedItem}">
</i:InvokeCommandAction>
</i:EventTrigger>
</i:Interaction.Triggers>
</DataGrid>
Hope this snippet helps.
You can add a SelectedItem property in the ViewModel. Bind that to DataGrid's SelectedItem. Now you know which item the user has selected (clicked on).
In the DataGrid's MouseDown or MouseUp event you can open the new Window with the same ViewModel object as the DataContext. That way the new Window knows which item was selected. Bind the new Window's fields to the SelectedItem in the ViewModel.
If you have set up the INotifyPropertyChanged correctly, the values that are changed in the new Window also will show up in the DataGrid in the first Window.
Since the SelectedItem also is part of the collection, you will automatically have the changed values in the collection as well. (I assume this is what you mean with "save the changed information").
if your collection, that is bound to the DataGrid, is a collection of Person objects, the "SelectedItem property" could look like this:
public Person SelectedPerson
{
get
{
return _selectedPerson;
}
set
{
_selectedPerson = value;
PropertyChanged....
}
}
private Person _selectedPerson;
ViewModel.cs
namespace Procect1
{
public class ViewModel : INotifyPropertyChanged
{
public ViewModel()
{
ListOfPeople = new ObservableCollection<Person>();
}
string image;
private void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
private void Window2OpenExecute()
{
Window2 Window2 = new Window2(this);
Window2.Show();
}
private void AddPhotoExecute()
{
foreach (Window window in Application.Current.Windows)
{
if (window.GetType() == typeof(Window2))
{
OpenFileDialog op = new OpenFileDialog();
op.Title = "Select a picture";
op.Filter = "All supported graphics|*.jpg;*.jpeg;*.png|" +
"JPEG (*.jpg;*.jpeg)|*.jpg;*.jpeg|" +
"Portable Network Graphic (*.png)|*.png";
if (op.ShowDialog() == true)
{
(window as Window2).imgPhoto.Source = new BitmapImage(new Uri(op.FileName));
image = op.FileName;
}
}
}
}
private void AddPersonToListExecute()
{
foreach (Window window in Application.Current.Windows)
{
if (window.GetType() == typeof(Window2))
{
ListOfPeople.Add(new Person()
{
Name = (window as Window2).textBox_FirstName.Text,
Surname = (window as Window2).textBox_LastName.Text,
IdNumber = (window as Window2).textBox_IdNumber.Text,
Age = (window as Window2).textBox_Age.Text,
Image = image
});
(window as Window2).Close();
}
}
}
private void SerializeExecute()
{
Stream stream = File.OpenWrite(Environment.CurrentDirectory + "\\Serialization1.xml");
XmlSerializer people = new XmlSerializer(typeof(ObservableCollection<Person>));
people.Serialize(stream, ListOfPeople);
stream.Close();
}
private void DeSerializeExecute()
{
XmlSerializer deserializer = new XmlSerializer(typeof(ObservableCollection<Person>));
TextReader textReader = new StreamReader(Environment.CurrentDirectory + "\\Serialization1.xml");
ListOfPeople = (ObservableCollection<Person>)deserializer.Deserialize(textReader);
textReader.Close();
}
public ICommand Window2Open { get { return new RelayCommand(Window2OpenExecute); } }
public ICommand AddPersonToList { get { return new RelayCommand(AddPersonToListExecute); } }
public ICommand Serialize { get { return new RelayCommand(SerializeExecute); } }
public ICommand DeSerialize { get { return new RelayCommand(DeSerializeExecute); } }
public ICommand AddPhoto { get { return new RelayCommand(AddPhotoExecute); } }
public ObservableCollection<Person> ListOfPeople
{
get
{
return listOfPeople;
}
set
{
listOfPeople = value;
OnPropertyChanged("ListOfPeople");
}
}
public Person SelectedPerson
{
get
{
return _selectedPerson;
}
set
{
_selectedPerson = value;
OnPropertyChanged(nameof(SelectedPerson));
}
}
private Person _selectedPerson;
private ObservableCollection<Person> listOfPeople;
public event PropertyChangedEventHandler PropertyChanged;
}
}
Person.cs
namespace Procect1
{
public class Person : INotifyPropertyChanged
{
public string Name
{
get
{
return name;
}
set
{
name = value;
OnPropertyChanged(nameof(Name));
}
}
public string Surname
{
get
{
return surname;
}
set
{
surname = value;
OnPropertyChanged(nameof(Surname));
}
}
public string IdNumber
{
get
{
return idNumber;
}
set
{
idNumber = value;
OnPropertyChanged(nameof(IdNumber));
}
}
public string Age
{
get
{
return age;
}
set
{
age = value;
OnPropertyChanged(nameof(Age));
}
}
public string Image
{
get
{
return image;
}
set
{
image = value;
OnPropertyChanged(nameof(Image));
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
private string name;
private string surname;
private string idNumber;
private string age;
private string image;
}
}
MainWindow.xaml
<Window x:Class="Procect1.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:Procect1"
mc:Ignorable="d"
Title="Lista pacjentów" Height="450" Width="800">
<Window.DataContext>
<local:ViewModel/>
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<!--<RowDefinition Height="auto" />-->
<!--<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>-->
</Grid.RowDefinitions>
<DataGrid Name="listView" MouseUp="listView_MouseUp" ItemsSource="{Binding ListOfPeople}" AutoGenerateColumns="False" SelectedItem="{Binding Index}">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Name, UpdateSourceTrigger=PropertyChanged}" Header="Imię" />
<DataGridTextColumn Binding="{Binding Surname}" Header="Nazwisko" />
<DataGridTextColumn Binding="{Binding IdNumber}" Header="Pesel" />
<DataGridTextColumn Binding="{Binding Age}" Header="Wiek" />
<DataGridTemplateColumn Header="Zdjęcie" Width=" 100">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Image Source="{Binding Image}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
<Canvas>
<Button Canvas.Right="20" Canvas.Top="30" Height="50" Width="80" FontSize="18" Command="{Binding Window2Open}">Dodaj</Button>
<Button Canvas.Right="20" Canvas.Top="100" Height="50" Width="80" FontSize="18" Command="{Binding Serialize}">Serializuj</Button>
<Button Canvas.Right="20" Canvas.Top="170" Height="50" Width="80" FontSize="18" Command="{Binding DeSerialize}">Załaduj</Button>
</Canvas>
</Grid>
</Window>
MainWindow.cs
public partial class MainWindow : Window
{
public MainWindow( )
{
InitializeComponent();
}
private void listView_MouseUp(object sender, MouseButtonEventArgs e)
{
Window3 window3 = new Window3();
window3.Show();
}
}
Window2.xaml
<Window x:Class="Procect1.Window2"
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:Procect1"
mc:Ignorable="d"
Title="Dane Pacjenta" Height="600" Width="500">
<Canvas>
<Label Canvas.Left="20" Canvas.Top="20" FontSize="24">Imię</Label>
<TextBox x:Name="textBox_FirstName" Canvas.Left="190" Canvas.Top="30" Height="28" Width="200" ></TextBox>
<Label Canvas.Left="20" Canvas.Top="100" FontSize="24">Nazwisko</Label>
<TextBox x:Name="textBox_LastName" Canvas.Left="190" Canvas.Top="110" Height="28" Width="200"></TextBox>
<Label Canvas.Left="20" Canvas.Top="180" FontSize="24">Pesel</Label>
<TextBox x:Name="textBox_IdNumber" Canvas.Left="190" Canvas.Top="190" Height="28" Width="200"></TextBox>
<Label Canvas.Left="20" Canvas.Top="260" FontSize="24">Wiek</Label>
<TextBox x:Name="textBox_Age" Canvas.Left="190" Canvas.Top="270" Height="28" Width="200"></TextBox>
<Label Canvas.Left="20" Canvas.Top="340" FontSize="24">Zdjęcie</Label>
<Image Name="imgPhoto" Canvas.Left="190" Canvas.Top="350" Height="120" Width="150"></Image>
<Button Canvas.Right="20" Canvas.Top="400" FontSize="16" Command="{Binding AddPhoto}" >Dodaj zdjęcie</Button>
<Button Canvas.Left="250" Canvas.Bottom="20" Height="40" Width="80" FontSize="24" Command="{Binding AddPersonToList}">Zapisz</Button>
</Canvas>
</Window>
MainWindow2.cs
public partial class Window2 : Window
{
public Window2(ViewModel viewModel)
{
InitializeComponent();
DataContext = viewModel;
}
}
Window3.xaml
<Window x:Class="Procect1.Window3"
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:Procect1"
mc:Ignorable="d"
Title="Dane Pacjenta" Height="600" Width="500">
<Window.DataContext>
<local:ViewModel/>
</Window.DataContext>
<Canvas>
<Label Canvas.Left="20" Canvas.Top="20" FontSize="24">Imię</Label>
<TextBox x:Name="textBox_FirstName" Text="{Binding SelectedPerson.Name}" Canvas.Left="190" Canvas.Top="30" Height="28" Width="200" ></TextBox>
<Label Canvas.Left="20" Canvas.Top="100" FontSize="24">Nazwisko</Label>
<TextBox x:Name="textBox_LastName" Text="{Binding SelectedPerson.Surname}" Canvas.Left="190" Canvas.Top="110" Height="28" Width="200"></TextBox>
<Label Canvas.Left="20" Canvas.Top="180" FontSize="24">Pesel</Label>
<TextBox x:Name="textBox_IdNumber" Text="{Binding SelectedPerson.IdNumber}" Canvas.Left="190" Canvas.Top="190" Height="28" Width="200"></TextBox>
<Label Canvas.Left="20" Canvas.Top="260" FontSize="24">Wiek</Label>
<TextBox x:Name="textBox_Age" Text="{Binding SelectedPerson.Age}" Canvas.Left="190" Canvas.Top="270" Height="28" Width="200"></TextBox>
<Label Canvas.Left="20" Canvas.Top="340" FontSize="24">Zdjęcie</Label>
<Image Name="imgPhoto" Source="{Binding SelectedPerson.Image}" Canvas.Left="190" Canvas.Top="350" Height="120" Width="150"></Image>
<Button Canvas.Right="20" Canvas.Top="400" FontSize="16" Command="{Binding AddPhoto}" >Dodaj zdjęcie</Button>
<Button Canvas.Left="250" Canvas.Bottom="20" Height="40" Width="80" FontSize="24">Zapisz</Button>
</Canvas>
</Window>
In Window3.cs I haven't added any code.
RylayCommand.cs
public class RelayCommand : ICommand
{
private readonly Func<bool> _canExecute;
private readonly Action _execute;
public RelayCommand(Action execute)
: this(execute, null)
{
}
public RelayCommand(Action execute, Func<bool> canExecute)
{
if (execute == null)
throw new ArgumentNullException("execute");
_execute = execute;
_canExecute = canExecute;
}
public event EventHandler CanExecuteChanged
{
add
{
if (_canExecute != null)
CommandManager.RequerySuggested += value;
}
remove
{
if (_canExecute != null)
CommandManager.RequerySuggested -= value;
}
}
public bool CanExecute(object parameter)
{
return _canExecute == null ? true : _canExecute();
}
public void Execute(object parameter)
{
_execute();
}
}
I post it as an answer because I couldn't add it to the question.
On MainWindow you have bound the SelectedItem to "Item". If you look at the Output window (if you are using Visual Studio) when you are running the program, you find following error:
System.Windows.Data Error: 40 : BindingExpression path error: 'Index' property not found on 'object' ''ViewModel'
XAML is a very quite beast, so look there first if the bindings doesn't work.
You should bind the SelectedItem to the SelectedPerson in the ViewModel.
SelectedItem="{Binding SelectedPerson}"
To get the window3 to work, you need to set the DataContext to the main windows DataContext (the view model).
Window3 window3 = new Window3();
window3.DataContext = this.DataContext;
window3.Show();
I have a ContextMenu that suppose to set value on its parent TextBox.
The textbox cannot have a name (by requirement), so I am setting it as CommandTarget
<TextBox Text="{Binding TextBoxOne, UpdateSourceTrigger=LostFocus}">
<TextBox.ContextMenu>
<ContextMenu>
<MenuItem Header="Set to 35"
Command="{Binding SetAmountCommand}"
CommandParameter="35"
CommandTarget="{Binding Text, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type TextBox}}}" />
<MenuItem Header="Set to 50"
Command="{Binding SetAmountCommand}"
CommandParameter="50"
CommandTarget="{Binding Text, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type TextBox}}}" />
</ContextMenu>
</TextBox.ContextMenu>
How to access the TextBox.Text from inside the Command ?
ViewModel
public class MainVm : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public string TextBoxOne { get; set; } = "One";
private ICommand _setAmountCommand;
public ICommand SetAmountCommand
{
get
{
return _setAmountCommand ?? (_setAmountCommand = new CommandParameterHandler((o) =>
{
object param = o;
double amount = (double)o;
//MyParentTextBox.Text = amount; //What to put here ? (Cannot be TextBoxOne = amount, need to route from View)
}, true));
}
}
}
Generic CommandParameterHandler
public class CommandParameterHandler : ICommand
{
private Action<object> _action;
private bool _canExecute;
public CommandParameterHandler(Action<object> action, bool canExecute)
{
_action = action;
_canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return _canExecute;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
_action(parameter);
}
}
You can only pass one CommandParameter to the command. If you want to pass is something in addition to the actual value, you could create a custom composite type that carries more than one value:
public class CompositeParameter : Freezable
{
protected override Freezable CreateInstanceCore()
{
return this;
}
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register(nameof(Value),
typeof(string), typeof(CompositeParameter));
public string Value
{
get { return (string)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
public static readonly DependencyProperty ControlProperty = DependencyProperty.Register(nameof(Control),
typeof(FrameworkElement), typeof(CompositeParameter));
public FrameworkElement Control
{
get { return (FrameworkElement)GetValue(ControlProperty); }
set { SetValue(ControlProperty, value); }
}
}
View Model:
public ICommand SetAmountCommand
{
get
{
return _setAmountCommand ?? (_setAmountCommand = new CommandParameterHandler((o) =>
{
CompositeParameter param = o as CompositeParameter;
if (param != null)
{
double amount = Convert.ToDouble(param.Value);
//...
TextBox textBox = param.Control as TextBox;
if (textBox != null)
textBox.Text = param.Value;
}
}, true));
}
}
View:
<TextBox Text="{Binding TextBoxOne, UpdateSourceTrigger=LostFocus}">
<TextBox.ContextMenu>
<ContextMenu>
<ContextMenu.Resources>
<local:CompositeParameter x:Key="paramA"
Value="35"
Control="{Binding PlacementTarget, RelativeSource={RelativeSource AncestorType=ContextMenu}}" />
<local:CompositeParameter x:Key="paramB"
Value="50"
Control="{Binding PlacementTarget, RelativeSource={RelativeSource AncestorType=ContextMenu}}" />
</ContextMenu.Resources>
<MenuItem Header="Set to 35"
Command="{Binding SetAmountCommand}"
CommandParameter="{StaticResource paramA}" />
<MenuItem Header="Set to 50"
Command="{Binding SetAmountCommand}"
CommandParameter="{StaticResource paramB}" />
</ContextMenu>
</TextBox.ContextMenu>
</TextBox>
After 2 days searching for answer, I came across this RoutedCommand tutorial. Yes, you can access CommandTarget from Command, but it has to be a static RoutedCommand. This approach fits the need as SetAmountCommand is shared by multiple MenuItem.
XAML
<Window x:Class="WpfCommandTargetDemo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfCommandTargetDemo">
<Window.CommandBindings>
<CommandBinding CanExecute="SetAmountCommand_CanExecute"
Command="{x:Static local:CustomRoutedCommand.SetAmountCommand}"
Executed="SetAmountCommand_Executed" />
</Window.CommandBindings>
<StackPanel>
<TextBox Text="{Binding TextBoxOne, UpdateSourceTrigger=LostFocus}">
<TextBox.ContextMenu>
<ContextMenu>
<MenuItem Header="Set to 35"
Command="{x:Static local:CustomRoutedCommand.SetAmountCommand}"
CommandParameter="35"
CommandTarget="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=ContextMenu}, Path=PlacementTarget}" />
<MenuItem Header="Set to 50"
Command="{x:Static local:CustomRoutedCommand.SetAmountCommand}"
CommandParameter="50"
CommandTarget="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=ContextMenu}, Path=PlacementTarget}" />
</ContextMenu>
</TextBox.ContextMenu>
</TextBox>
</StackPanel>
</Window>
CodeBehind
public partial class MainWindow : Window
{
private readonly MainVm _mainVm;
public MainWindow()
{
InitializeComponent();
_mainVm = new MainVm();
DataContext = _mainVm;
}
void SetAmountCommand_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
}
void SetAmountCommand_Executed(object sender, ExecutedRoutedEventArgs e)
{
object param = e.Parameter; //CommandParameter
TextBox textbox = e.OriginalSource as TextBox; //CommandTarget
if (textbox != null)
{
textbox.Text = param.ToString();
}
}
}
RoutedCommand has to be static, because it is statically bound to XAML element.
public static class CustomRoutedCommand
{
public static readonly RoutedCommand SetAmountCommand = new RoutedCommand();
}
For completeness, I cannot have the Command on my ViewModel. SetAmountCommand property is removed.
public class MainVm : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public string TextBoxOne { get; set; } = "One";
}
The problem is that the binding of ViewModel properties to the control`s properties does not work correctly. I checked the properties and their values change, but the visibility of the controls does not change. Any idea what is involved in this? Or am I missing something?
ViewModel:
class MainViewModel
{
public LoginViewModel LoginViewModel { get; set; }
Notifier notifier = new Notifier();
public MainViewModel()
{
LoginViewModel = new LoginViewModel();
}
private Visibility mdiPanelVisibility=Visibility.Visible;
public Visibility MDIPanelVisibility
{
get
{
return mdiPanelVisibility;
}
set
{
mdiPanelVisibility = value;
NotifyPropertyChanged("MDIPanelVisibility");
}
}
private RelayCommand showMDIPanelCommand;
public RelayCommand ShowMDIPanelCommand
{
get
{
return showMDIPanelCommand ??
(showMDIPanelCommand = new RelayCommand(obj =>
{
MDIPanelVisibility = Visibility.Visible;
}));
}
}
private RelayCommand hideMDIPanelCommand;
public RelayCommand HideMDIPanelCommand
{
get
{
return hideMDIPanelCommand ??
(hideMDIPanelCommand = new RelayCommand(obj =>
{
MDIPanelVisibility = Visibility.Hidden;
}));
}
}
private event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
and View:
<Border Visibility="{Binding MDIPanelVisibility}">
<Border.InputBindings>
<MouseBinding MouseAction="LeftClick" Command="{Binding HideMDIPanelCommand}"/>
</Border.InputBindings>
</Border>
<ContentPresenter Width="Auto" Grid.RowSpan="2" Panel.ZIndex="1" VerticalAlignment="Center" Visibility="{Binding MDIPanelVisibility}">
<ContentPresenter.Content>
<local:MDIView/>
</ContentPresenter.Content>
</ContentPresenter>
<Button Content="Личный кабинет" FontSize="13" Command="{Binding ShowMDIPanelCommand}">
<Button.Style>
<Style TargetType="Button" BasedOn="{StaticResource aLogButton}"/>
</Button.Style>
</Button>
The MainViewModel class needs to inherit from INotifyPropertyChanged, which your class does not, in order for the binding framework to behave as expected when the view's DataContext is set to the MainViewModel instance.
Update class definition
public class MainViewModel: INotifyPropertyChanged {
//...
}
Trying to learn WPF and I have read/tested with a tutorial.
This is my scenario:
A wpf C# application.
My main window has a UserControl on it.
This UserControl has 4 buttons on it.
My intent is to bind each command(click) event to each button.
But instead of binding each button to its own class I want to bind each command event of these 4 buttons to 1 class.
So.. I wanted to pass a parameter to the CanExecute and Execute methods and I was/am trying to pass an enum to these methods.
so.. what i have got so far is this:
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public bool CanExecute(object parameter)
{
var commandChosen= parameter as TopMenuCommands;
return true;
}
public void Execute(object parameter)
{
var buttonChosen = parameter as MenuCommandObject;
evMenuChange(buttonChosen);
}
public enum enTopMenuCommands
{
Button1 = 0,
Button1 = 1,
Button1 = 2,
Button1 = 3
}
But how can I tie this to my main window?
I admit I maybe doing this all completely wrong but I am still learning.
thanks
My ICommand implementation takes an Action<object> in the constructor. The Execute method just invoked the Action.
That way the logic for each command is passed in from where it is created.
ICommand Implementation:
public class SimpleCommand : ICommand
{
public event EventHandler CanExecuteChanged;
private Action<object> _execute;
private Func<bool> _canExecute;
public SimpleCommand(Action<object> execute) : this(execute, null) { }
public SimpleCommand(Action<object> execute, Func<bool> canExecute)
{
_execute = execute;
_canExecute = canExecute;
}
public bool CanExecute(object param)
{
if (_canExecute != null)
{
return _canExecute.Invoke();
}
else
{
return true;
}
}
public void Execute(object param)
{
_execute.Invoke(param);
}
protected void OnCanExecuteChanged()
{
CanExecuteChanged?.Invoke(this,EventArgs.Empty);
}
#region Common Commands
private static SimpleCommand _notImplementedCommand;
public static ICommand NotImplementedCommand
{
get
{
if (_notImplementedCommand == null)
{
_notImplementedCommand = new SimpleCommand(o => { throw new NotImplementedException(); });
}
return _notImplementedCommand;
}
}
#endregion
}
Usage Example:
using System;
using System.Data.Entity;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Windows.Input;
using SqlMetaQuery.Model;
using SqlMetaQuery.ViewModels.ScriptList;
using SqlMetaQuery.Windows.EditQuery;
using WpfLib;
namespace SqlMetaQuery.Windows.Main
{
class MainWindowVm : WpfLib.ViewModel
{
public MainWindowVm()
{
if (!IsInDesignMode)
{
using (Context db = new Context())
{
ScriptTree = new ScriptTreeVm(db.Tags
.Include(t => t.Scripts)
.OrderBy(t => t.Name));
CurrentUser = db.Users.Where(u => u.UserName == "Admin").AsNoTracking().FirstOrDefault();
MiscTag = db.Tags.Where(t => t.Name == "Misc").AsNoTracking().FirstOrDefault();
}
}
}
public ScriptTreeVm ScriptTree { get; }
public Model.User CurrentUser { get; }
private Model.Tag MiscTag { get; }
private void SaveScript(Model.Script script)
{
using (var context = new Model.Context())
{
context.Scripts.Add(script);
context.SaveChanges();
}
}
#region Commands
private ICommand _exitCommand;
public ICommand ExitCommand
{
get
{
if (_exitCommand == null)
{
_exitCommand = new SimpleCommand((arg) => WindowManager.CloseAll());
}
return _exitCommand;
}
}
private ICommand _newScriptCommand;
public ICommand NewScriptCommand
{
get
{
if (_newScriptCommand == null)
{
_newScriptCommand = new SimpleCommand((arg) =>
{
var script = new Model.Script()
{
Title = "New Script",
Description = "A new script.",
Body = ""
};
script.Tags.Add(MiscTag);
var vm = new EditQueryWindowVm(script);
var result = WindowManager.DisplayDialogFor(vm);
// if (result.HasValue && result.Value)
//{
script.VersionCode = Guid.NewGuid();
script.CreatedBy = CurrentUser;
script.CreatedDate = DateTime.Now.ToUniversalTime();
SaveScript(script);
//}
});
}
return _newScriptCommand;
}
}
#endregion
}
}
XAML:
<Window x:Class="SqlMetaQuery.Windows.Main.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:SqlMetaQuery.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:SqlMetaQuery.Windows.Main"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="MainWindow"
Width="800"
Height="600"
d:DataContext="{d:DesignInstance Type=local:MainWindowVm}"
mc:Ignorable="d">
<DockPanel>
<Menu DockPanel.Dock="Top">
<MenuItem Header="File">
<MenuItem Command="{Binding Path=NewScriptCommand}" Header="New Script..." />
<Separator />
<MenuItem Command="{Binding Path=ExitCommand}" Header="Exit" />
</MenuItem>
</Menu>
<Grid Margin="8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="250" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<controls:ScriptTree Grid.Row="0"
Grid.Column="0"
DataContext="{Binding Path=ScriptTree}" />
<GridSplitter Grid.Row="0"
Grid.Column="1"
Width="8"
VerticalAlignment="Stretch"
ResizeBehavior="PreviousAndNext" />
<Grid Grid.Row="0" Grid.Column="2">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="8" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Border Grid.Row="0" Style="{StaticResource BorderStandard}">
<TextBlock Text="{Binding Path=ScriptTree.CurrentScript.Title}" />
</Border>
<Border Grid.Row="3" Style="{StaticResource BorderStandard}">
<TextBlock Text="{Binding Path=ScriptTree.CurrentScript.Body}" />
</Border>
</Grid>
</Grid>
</DockPanel>
</Window>
I have a DataGrid and a ListView in my application. The ListView provides information about the datagrid's selected item. I put a HyperLink in that ListView that should change the datagrid's selected item to the "parent" of the currently selected item.
My approach is to set SelectedItemin my code behind. Everything works quite well but the datagrid doesn't highlight the new selected item. But I can clearly see it is selected because off its gray background color. Is it possible to set the highlighted cell programmatically?
<ListView>
<TextBlock Text="{Binding SelectedParameter.Definition.Name, StringFormat=Name: {0:C}}"
TextWrapping="Wrap"/>
<TextBlock Text="{Binding SelectedParameter.Definition.Type, StringFormat=Datentyp: {0:C}}"
TextWrapping="Wrap"/>
TextWrapping="Wrap"/>
<Hyperlink Command="{Binding GoToMasterParameterCommand}">
Masterparameter
</Hyperlink>
</ListView>
<DataGrid Name="m_DataGrid"
ItemsSource="{Binding Path=Parameters}"
SelectedItem="{Binding SelectedParameter}"
SelectionMode="Single"
AutoGenerateColumns="False"
TargetUpdated="m_ParameterDataGrid_TargetUpdated">
<DataGrid.Columns>
<DataGridTextColumn
Header="ID"
Binding="{Binding Id}"
IsReadOnly="True"/>
<DataGridTextColumn
Header="Value"
Binding="{Binding Value.CurrentInternalValue, NotifyOnTargetUpdated=True}"
</DataGrid.Columns>
</DataGrid>
internal void GoToMasterParameter()
{
string parentId = GetParentId(this.SelectedParameter);
this.SelectedParameter = this.Parameters.Single(item => item.Id == parentId);
}
The problem you have is that the selected rows/cells are not focused as the focus is still in your listView item. What you could do is style the DataGridCell element in xaml. Here is a small piece of code that demonstrate this:
<Window.Resources>
<Style TargetType="{x:Type DataGridCell}">
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Background" Value="Red"/>
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<DataGrid ItemsSource="{Binding Tests}"
SelectedItem="{Binding GridSelectedItem, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
SelectedIndex="{Binding SelectedGridIndex, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
</DataGrid>
<Button Command="{Binding ChangeSelectedItemCommand}"
Content="Change Grid Selected item"
Grid.Column="1"
VerticalAlignment="Top"/>
</Grid>
and here the viewModel part:
public class MainWindowViewModel : INotifyPropertyChanged
{
#region Private members
private List<TestClass> _tests;
private TestClass _gridSelectedItem;
private ICommand _changeSelectedItemCommand;
private int _selectedGridIndex;
#endregion
#region Constructor
public MainWindowViewModel()
{
Tests = new List<TestClass>();
for (int i = 0; i < 25; i++)
{
TestClass testClass= new TestClass {Name = "Name " + i, Title = "Title" + i};
Tests.Add(testClass);
}
}
#endregion
#region Public properties
public List<TestClass> Tests
{
get { return _tests; }
set
{
_tests = value;
OnPropertyChanged("Tests");
}
}
public TestClass GridSelectedItem
{
get { return _gridSelectedItem; }
set
{
_gridSelectedItem = value;
OnPropertyChanged("GridSelectedItem");
}
}
public int SelectedGridIndex
{
get { return _selectedGridIndex; }
set
{
_selectedGridIndex = value;
OnPropertyChanged("SelectedGridIndex");
}
}
#endregion
public ICommand ChangeSelectedItemCommand
{
get { return _changeSelectedItemCommand ?? (_changeSelectedItemCommand = new SimpleCommand(p => ChangeSelectedGridItem())); }
}
private void ChangeSelectedGridItem()
{
SelectedGridIndex++;
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
the demo objects class:
public class TestClass
{
public string Title { get; set; }
public string Name { get; set; }
}
and some command class:
public class SimpleCommand : ICommand
{
private readonly Predicate<object> _canExecuteDelegate;
private readonly Action<object> _executeDelegate;
#region Constructors
public SimpleCommand(Action<object> execute)
: this(execute, null)
{
}
public SimpleCommand(Action<object> execute, Predicate<object> canExecute)
{
if (execute == null)
{
throw new ArgumentNullException("execute");
}
_executeDelegate = execute;
_canExecuteDelegate = canExecute;
}
#endregion // Constructors
#region ICommand Members
public virtual bool CanExecute(object parameter)
{
return _canExecuteDelegate == null || _canExecuteDelegate(parameter);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameter)
{
_executeDelegate(parameter);
}
#endregion
}
Make sure to add the DataContext of the View so that it knows about your ViewModel:
public MainWindow()
{
InitializeComponent();
DataContext = new MainWindowViewModel();
}
I hope this helps you obtain the desired effect.