I hope somebody can help me out here. Simplified the code for posting.
We have a main window (MvvmTestView) with a menu, and a 2nd window (SettingsView) which holds several tabs. I can open the SettingsView window alright. I can even select which Tab to open by setting this in the code.
How can I get back the correct value with the command parameter from the XAML code so that the correct tab opens?
MvvmTestView.xaml:
<Window x:Class="MvvmTest.Views.MvvmTestView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:MvvmTest.ViewModels"
WindowStartupLocation="CenterScreen"
Title="MvvmTestView"
Height="500"
Width="500">
<Window.DataContext>
<vm:MvvmTestViewModel/>
</Window.DataContext>
<Grid>
<DockPanel>
<Menu>
<MenuItem Header="Menu">
<MenuItem
Header="Tab01"
Command="{Binding SettingsViewCommand}"
CommandParameter="0"/>
<MenuItem
Header="Tab02"
Command="{Binding SettingsViewCommand}"
CommandParameter="1"/>
</MenuItem>
</Menu>
</DockPanel>
<DockPanel>
<Label Content="MainView" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</DockPanel>
</Grid>
</Window>
SettingView.xaml
<Window x:Class="MvvmTest.Views.SettingsView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:tabData="clr-namespace:MvvmTest.Views"
xmlns:vm="clr-namespace:MvvmTest.ViewModels"
WindowStartupLocation="CenterScreen"
Title="SettingsView"
Height="400"
Width="400">
<Window.DataContext>
<vm:MvvmTestViewModel/>
</Window.DataContext>
<Grid>
<TabControl
SelectedIndex="{Binding SettingsSelectedIndex, Mode=TwoWay}">
<tabData:Tab01View/>
<tabData:Tab02View/>
</TabControl>
</Grid>
</Window>
SettingsViewModel.cs
using System.ComponentModel;
namespace MvvmTest.ViewModels
{
public class SettingsViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string property)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
}
private int _settingsSelectedIndex;
public int SettingsSelectedIndex
{
get
{
return _settingsSelectedIndex;
}
set
{
_settingsSelectedIndex = value;
OnPropertyChanged("SettingsSelectedIndex");
}
}
}
}
MvvmTestViewModel.cs
using MvvmTest.Commands;
using MvvmTest.Views;
using System.ComponentModel;
using System.Windows.Input;
namespace MvvmTest.ViewModels
{
internal class MvvmTestViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private SettingsViewModel SettingsViewModel;
public MvvmTestViewModel()
{
SettingsViewModel = new SettingsViewModel();
SettingsViewCommand = new SettingsViewCommand(this);
}
public ICommand SettingsViewCommand
{
get;
private set;
}
public void SettingsWindow()
{
SetIndex();
SettingsView settingsView = new SettingsView()
{
DataContext = SettingsViewModel
};
settingsView.ShowDialog();
}
public int SetIndex()
{
SettingsViewModel.SettingsSelectedIndex = 1;
return SettingsViewModel.SettingsSelectedIndex;
}
}
}
SettingsViewCommand.cs
using MvvmTest.ViewModels;
using System;
using System.Windows.Input;
namespace MvvmTest.Commands
{
internal class SettingsViewCommand : ICommand
{
private MvvmTestViewModel settingsViewModel;
public SettingsViewCommand(MvvmTestViewModel settingsViewModel)
{
this.settingsViewModel = settingsViewModel;
}
public event EventHandler CanExecuteChanged
{
add
{
CommandManager.RequerySuggested += value;
}
remove
{
CommandManager.RequerySuggested -= value;
}
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
settingsViewModel.SettingsWindow();
}
}
}
I suggest to avoid creating multiple command classes like SettingsViewCommand : ICommand. Instead use some general-purpose command class (e.g. RelayCommand from MvvmFoundation NuGet package)
assuming you added MvvmFoundation to your project, refactor MvvmTestViewModel class like this:
internal class MvvmTestViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private SettingsViewModel SettingsViewModel;
public MvvmTestViewModel()
{
SettingsViewModel = new SettingsViewModel();
SettingsViewCommand = new RelayCommand<int>(SettingsWindow);
}
public ICommand SettingsViewCommand
{
get;
private set;
}
public void SettingsWindow(int index)
{
SettingsViewModel.SettingsSelectedIndex = index;
SettingsView settingsView = new SettingsView()
{
DataContext = SettingsViewModel
};
settingsView.ShowDialog();
}
}
CommandParameter from a view is passed to SettingsWindow method in a viewModel and used to change selected index
Related
This question already has an answer here:
WPF MenuItem style parameters not available on menu first open
(1 answer)
Closed 2 years ago.
Each ListBox item has a ContextMenu "Kick" option. It does trigger the CanKickPlayer() method when I right-click the first ListBox item for the first time, but never again if I repeat the process on the same or a different ListBox item. Questions:
How to do so CanKickPlayer() triggers every time I choose the ContextMenu "Kick" option?
Why is the passed parameter in CanKickPlayer() method null?
MainWindow.xaml
<Window x:Class="ContextMenuTriggeredOnce.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="300" Width="200">
<GroupBox Header="Players">
<ListBox ItemsSource="{Binding Players}" SelectedItem="{Binding SelectedPlayer}">
<ListBox.ContextMenu>
<ContextMenu>
<MenuItem Header="Kick" Command="{Binding KickPlayerCommand}" CommandParameter="{Binding SelectedPlayer}" />
</ContextMenu>
</ListBox.ContextMenu>
</ListBox>
</GroupBox>
</Window>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public MainViewModel MainViewModel { get; set; }
public MainWindow()
{
MainViewModel = new MainViewModel();
DataContext = MainViewModel;
InitializeComponent();
}
}
MainViewModel.cs
public class MainViewModel : BaseViewModel
{
public ObservableCollection<string> Players { get; set; } = new ObservableCollection<string>();
private string _selectedPlayer;
public string SelectedPlayer
{
get => _selectedPlayer;
set
{
if (value == null)
{
return;
}
SetProperty(ref _selectedPlayer, value);
}
}
private readonly RelayCommand _kickPlayerCommand;
public ICommand KickPlayerCommand => _kickPlayerCommand;
public MainViewModel()
{
Players.Add("Player1");
Players.Add("Player2");
_kickPlayerCommand = new RelayCommand(OnKickPlayer, CanKickPlayer);
}
private void OnKickPlayer(object command)
{
Players.Remove(command.ToString());
_kickPlayerCommand.InvokeCanExecuteChanged();
}
private bool CanKickPlayer(object command)
{
return command != null;
}
}
BaseViewModel.cs
public abstract class BaseViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected bool SetProperty<T>(ref T field, T newValue, [CallerMemberName] string propertyName = null)
{
if (!EqualityComparer<T>.Default.Equals(field, newValue))
{
field = newValue;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
return true;
}
return false;
}
}
RelayCommand.cs
class RelayCommand : ICommand
{
private readonly Action<object> _executeAction;
private readonly Func<object, bool> _canExecuteAction;
public event EventHandler CanExecuteChanged;
public RelayCommand(Action<object> executeAction, Func<object, bool> canExecuteAction)
{
_executeAction = executeAction;
_canExecuteAction = canExecuteAction;
}
public void Execute(object parameter) => _executeAction(parameter);
public bool CanExecute(object parameter) => _canExecuteAction?.Invoke(parameter) ?? true;
public void InvokeCanExecuteChanged() => CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
You need to set the CommandParameter before Command.
<MenuItem Header="Kick" CommandParameter="{Binding SelectedPlayer}" Command="{Binding KickPlayerCommand}" />
(I don't use the Command pattern. I prefer using the Click event and from the Click event handler calling a function in the ViewModel. If I need to disable the menu item, I use a bool property in the ViewModel and bind that to the IsEnabled property on the menu item.)
The following problem makes me crazy, for the last 2 days i spent my afternoons trying to solve the problem, so i really tried to research it.
My problem is that, when i'm using databinding to a treeview, it doesn't seem to work. I'm probably missing something, and i'd like to kindly ask your help finding it.
For the WPF XAML i'm using the following code:
<Window x:Class="MesDiag2.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:MesDiag2"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<HierarchicalDataTemplate ItemsSource="{Binding Materials}" DataType="{x:Type local:ProductNode}">
<Label Content="{Binding Product.LotName}"/>
</HierarchicalDataTemplate>
</Window.Resources>
<Grid>
<TreeView ItemsSource="{Binding Root}">
</TreeView>
</Grid>
My classes are the following, note that this is only a "test" project i made to see where the problem is. I've tried to make everything as simple as possible. The INotifyPropertyChanged for every class was just a desperate attempt to see if it fixes it:
using System.ComponentModel;
namespace MesDiag2
{
public class Product : INotifyPropertyChanged
{
string lotName;
public string LotName
{
get { return lotName; }
set
{
lotName = value;
NotifyPropertyChanged("LotName");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string name)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
}
The context of the TreeViewItem should be the following class:
using System.Collections.Generic;
using System.ComponentModel;
namespace MesDiag2
{
public class ProductNode : INotifyPropertyChanged
{
Product product;
List<Product> materials;
public ProductNode(Product product)
{
Materials = new List<Product>();
Product = product;
}
public Product Product
{
get { return product; }
set
{
product = value;
NotifyPropertyChanged("Product");
}
}
public List<Product> Materials
{
get { return materials; }
set
{
materials = value;
NotifyPropertyChanged("Materials");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string name)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
}
My ViewModel:
using System.ComponentModel;
namespace MesDiag2
{
public class ViewModel : INotifyPropertyChanged
{
public ProductNode Root { get; set; }
public string Test { get; set; }
public ViewModel()
{
Root = new ProductNode(new Product { LotName = "Test" });
Test = "Hello";
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string name)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
}
And finally the content of my MainWindow class:
using System.Windows;
namespace MesDiag2
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new ViewModel();
}
}
}
Its been a little while since I've done WPF, but I think you want to do something like this:
<TreeView ItemsSource="{Binding Root.Materials}">
<HierarchicalDataTemplate>
<Label Content="{Binding LotName}"/>
</HierarchicalDataTemplate>
</TreeView>
I'd also make:
List<Product> materials;
this:
ObservableCollection<Product> materials
https://learn.microsoft.com/en-us/dotnet/api/system.collections.objectmodel.observablecollection-1?view=netframework-4.8
I have some WPF code that looks like this
C#
namespace ItemEventTest
{
public partial class MainWindow : Window
{
public MainWindow()
{
DataContext = this;
MyItems = new ObservableCollection<MyItem>();
MyItems.Add(new MyItem { Name = "Otto" });
MyItems.Add(new MyItem { Name = "Dag" });
MyItems.Add(new MyItem { Name = "Karin" });
InitializeComponent();
}
public ObservableCollection<MyItem> MyItems { get; set; }
}
public class MyItem :INotifyPropertyChanged
{
private string m_name;
public string Name
{
get { return m_name; }
set
{
m_name = value;
OnPropertyChanged();
}
}
public void WhenMouseMove()
{
//Do stuff
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Xaml
<Window x:Class="ItemEventTest.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:ItemEventTest"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<ListBox ItemsSource="{Binding MyItems}">
<ListBox.ItemTemplate>
<DataTemplate DataType="local:MyItem">
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Window>
What I now want to do is when the mouse moves over an item is to call WhenMouseMove on the object that is the source of that item.
I would like to have the function called directly on the object and not by first going through MainWindow or some view model connected to MainWindow. It feels like it should be possible because the data is bound that way but I haven't managed to find a description of how to do it.
If you are seeking solution in MVVM pattern
Edit: Add a reference to System.Windows.Interactivity dll (which is system defined if Blend or VS2015 installed)
then add following namespace xmlns:i="schemas.microsoft.com/expression/2010/interactivity"
// xaml file
<Grid>
<ListBox ItemsSource="{Binding MyItems}">
<ListBox.ItemTemplate>
<DataTemplate DataType="local:MyItem">
<TextBlock Text="{Binding Name}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseEnter">
<i:InvokeCommandAction Command="{Binding MouseHoveredItemChangedCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
// viewmodel (MyItem class)
public void WhenMouseMove()
{
//Do stuff
Console.WriteLine(Name);
}
private RelayCommand _MouseHoveredItemChangedCommand;
public RelayCommand MouseHoveredItemChangedCommand
{
get
{
if (_MouseHoveredItemChangedCommand == null)
{
_MouseHoveredItemChangedCommand = new RelayCommand(WhenMouseMove);
}
return _MouseHoveredItemChangedCommand;
}
}
// RelayCommand class
public class RelayCommand : ICommand
{
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
private Action methodToExecute;
private Func<bool> canExecuteEvaluator;
public RelayCommand(Action methodToExecute, Func<bool> canExecuteEvaluator)
{
this.methodToExecute = methodToExecute;
this.canExecuteEvaluator = canExecuteEvaluator;
}
public RelayCommand(Action methodToExecute)
: this(methodToExecute, null)
{
}
public bool CanExecute(object parameter)
{
if (this.canExecuteEvaluator == null)
{
return true;
}
else
{
bool result = this.canExecuteEvaluator.Invoke();
return result;
}
}
public void Execute(object parameter)
{
this.methodToExecute.Invoke();
}
}
I can figure out how to bind a property to a textbox in the codebehind, but with my current application I need to bind to a property from a different class. Here's a simplified version of what I have:
<Window x:Class="Project1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<Grid>
<TextBox x:Name="Textbox1" Text="{Binding Class1.Class2.TextToBind, Mode=TwoWay}" Height="20" Width="75" Background="#FFE5E5E5"/>
</Grid>
Codebehind:
namespace Project1
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
Class1 = new Class1();
}
public Class1 Class1 { get; set; }
}
}
Class1:
namespace Project1
{
public class Class1
{
public Class1()
{
Class2 = new Class2();
}
public Class2 Class2 { get; set; }
}
}
Final class:
namespace Project1
{
public class Class2
{
public Class2()
{
}
private string textToBind;
public string TextToBind { get { return textToBind; } set { SetProperty(ref textToBind, value); } }
public event PropertyChangedEventHandler PropertyChanged;
private void SetProperty<T>(ref T field, T value, [CallerMemberName] string name = "")
{
if (!EqualityComparer<T>.Default.Equals(field, value))
{
field = value;
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
}
}
You have to set DataContext for your TextBox or for your Window
namespace Project1
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
Class1 = new Class1();
Class1.Class2.TextToBind = "Test";
this.DataContext = this;
}
public Class1 Class1 { get; set; }
}
}
Also you need to inherit Class2 from INotifyPropertyChanged:
public class Class2 : INotifyPropertyChanged
{
public Class2()
{
}
private string textToBind;
public string TextToBind { get { return textToBind; } set { SetProperty(ref textToBind, value); } }
public event PropertyChangedEventHandler PropertyChanged;
private void SetProperty<T>(ref T field, T value, [CallerMemberName] string name = "")
{
if (!EqualityComparer<T>.Default.Equals(field, value))
{
field = value;
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
}
If you want to bind your textbox to a property from a class other than the datacontext of the window, you need to set it explicitly
I tried this code and it worked
<Window x:Class="Project1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
<!-- first add namespace of your project-->
xmlns:local="clr-namespace:Project1"
Title="Window1" Height="300" Width="300">
<!--second define your data context class as resource-->
<Window.Resources >
<local:Class2 x:Key="class2"></local:Class2>
</Window.Resources>
<Grid>
<TextBox x:Name="Textbox1" Text="{Binding TextToBind, Mode=TwoWay}" Height="20" Width="75" Background="#FFE5E5E5">
<!--third set the data context of the textbox Explicitly-->
<TextBox.DataContext>
<StaticResourceExtension ResourceKey="class2"/>
</TextBox.DataContext>
</TextBox>
</Grid>
</Window>
Please note : if you are going to set the property TextToBind programmatically and you want your UI to show the result, you have to implement INotifyPropertyChanged.
I'm trying to create a simple WPF Application using data binding.
The code seems fine, but my view is not updating when I'm updating my property.
Here's my XAML:
<Window x:Class="Calculator.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:calculator="clr-namespace:Calculator"
Title="MainWindow" Height="350" Width="525"
Name="MainWindowName">
<Grid>
<Label Name="MyLabel" Background="LightGray" FontSize="17pt" HorizontalContentAlignment="Right" Margin="10,10,10,0" VerticalAlignment="Top" Height="40"
Content="{Binding Path=CalculatorOutput, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
</Window>
Here's my code-behind:
namespace Calculator
{
public partial class MainWindow
{
public MainWindow()
{
DataContext = new CalculatorViewModel();
InitializeComponent();
}
}
}
Here's my view-model
namespace Calculator
{
public class CalculatorViewModel : INotifyPropertyChanged
{
private String _calculatorOutput;
private String CalculatorOutput
{
set
{
_calculatorOutput = value;
NotifyPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
I'm can't see what I am missing here?? o.O
CalculatorOutput has no getter. How should the View get the value? The Property has to be public as well.
public String CalculatorOutput
{
get { return _calculatorOutput; }
set
{
_calculatorOutput = value;
NotifyPropertyChanged();
}
}