I have a problem with referencing properties or elements on different user controls in my MVVM WPF application.
Edit: Reduced code to a MCVE (hopefully). Also removed PropertyChanged Events to reduce code.
TLDR
I split up all my MainWindow.xaml elements to different user controls
In the menubar (one control) I want to fire a ICommand to save a Settings object (which is the datacontext of the MainWindow
For the method that is called in the Command I need a value from a completely different UserControl that is neither parent nor child of the menubar
How can I retrieve the value from the PasswordBox in a MVVM approach (most preferably as the CommandParameter on the MenuBar)?
View
MainWindow.xaml
<Window x:Class="WpfApp1.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:WpfApp1.ViewModel"
xmlns:View="clr-namespace:WpfApp1.View"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<ViewModel:SettingsViewModel/>
</Window.DataContext>
<StackPanel>
<DockPanel>
<View:MenuBar DataContext="{Binding}"/>
</DockPanel>
<TabControl>
<TabItem Header="Tab1" DataContext="{Binding AppSettings}">
<View:TabItemContent/>
</TabItem>
</TabControl>
</StackPanel>
</Window>
The MenuBar which is supposed to bind on ICommand properties on the ViewModel (the DataContext of MainWindow).
MenuBar.xaml
<UserControl x:Class="WpfApp1.View.MenuBar"
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:WpfApp1.View"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800" >
<Menu DockPanel.Dock="Top" Height="Auto" >
<!-- In the command I need a reference to the password on the tabItem -->
<MenuItem Name="SaveItem" Height="Auto" Header="Save"
Command="{Binding SaveCommand}"
CommandParameter="Binding RelativeSource=
{RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}"/>
</Menu>
</UserControl>
TabItemContent.xaml
<UserControl x:Class="WpfApp1.View.TabItemContent"
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:WpfApp1.View"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
DataContext="{Binding PasswordSettings}">
<Grid>
<PasswordBox Height="25" Width="100" />
</Grid>
</UserControl>
In TabItemControl.xaml.cs I tried to introduce a Dependency Property and set the value to the datacontext of the control.
ViewModel
SettingViewModel.cs
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows.Input;
using WpfApp1.Commands;
using WpfApp1.Model;
namespace WpfApp1.ViewModel
{
public class SettingsViewModel
{
public Settings AppSettings { get; private set; } = new Settings();
public ICommand SaveCommand { get; private set; }
public SettingsViewModel()
{
SaveCommand = new DelegateCommand( SaveSettings );
// load settings and call
// OnPropertyChanged( "AppSettings" );
}
public void SaveSettings( object o = null )
{
if( o is string encryptedPass )
{
// get the password and save it to AppSettings object
}
// call save method on settings
}
}
}
Model (Setting classes)
AppSetting.cs (encapsulating all the setting objects from the other tabs)
namespace WpfApp1.Model
{
public class Settings
{
public PasswordSettings PwSettings { get; set; } = new PasswordSettings();
}
}
PasswordSettings.cs
namespace WpfApp1.Model
{
public class PasswordSettings
{
public string EncryptedPass { get; set; }
}
}
and finally the DelegateCommand implementation in
DelegateCommand.cs see this gist
Related
Someone please help me understand how my View is reaching my ViewModel.
My "BookView" has a Listview control that retrieves its data from a binding to a property in the "BookViewModel" using no namespaces. But the ViewModel is in a whole separate folder in the project so I wondering how it is finding it.
<UserControl x:Class="ContactBook.Views.BookView"
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:ContactBook.Views"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800" Background="White">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ListView Grid.Column="0"
ItemsSource="{Binding Path=ContactsVM.Contacts}"
SelectedItem="{Binding Path=ContactsVM.SelectedContact}">
</ListView>
</Grid>
</Grid>
</UserControl>
This is my view model with the ContactVM property in it.
public class BookViewModel : ObservableObject
{
public ContactsViewModel ContactsVM
{
get { return _contactsVM; }
set { OnPropertyChanged(ref _contactsVM, value); }
}
public BookViewModel()
{
}
}
These 2 files are in different folders in the project.
image
I want to implement a simple Close Button in a WPF Window. The Window basically looks like this:
<Window x:Class="MyApplication.MainWindow"
xmlns=""....
...."
Title="MainWindow" WindowState="Maximized" WindowStartupLocation="CenterScreen" x:Name="mainWindow">
<DockPanel LastChildFill="True">
<Ribbon DockPanel.Dock="Top">
<Ribbon.ApplicationMenu>
<RibbonApplicationMenu SmallImageSource="Resources/menu_16x16.png">
<RibbonApplicationMenu.FooterPaneContent>
<RibbonButton Label="Beenden"
Command="{Binding CmdCloseApp}"
CommandParameter="{Binding ElementName=mainWindow}"
SmallImageSource="Resources/ende_16x16.png"
HorizontalAlignment="Right"/>
</RibbonApplicationMenu.FooterPaneContent>
</RibbonApplicationMenu>
</Ribbon.ApplicationMenu>
</Ribbon>
</DockPanel>
</Window>
DataContext of this Window ist set in it's Code-behindt to an instance of MainWindowViewModel
MainWindowViewModel:
public class MainWindowViewModel
{
public ICommand CmdCloseApp { get; set; }
public MainWindowViewModel()
{
CmdCloseApp = new RelayCommand<Window>(CloseApp);
}
public void CloseApp(Window w)
{
w.Close();
}
}
In CloseAppw is always null. Alle the other commands with string paramters, etc.. work perfectly - my only problem is that i don't get the window element does not find a way to my viewmodel.
thanks for your help!
EDIT
I am so sorry, i tried it with a simple button and it worked - The problem only occurs with RibbonButton
Edit: After your change to application menu RibbonButton your issue is that Microsoft RibbonControlLibrary uses a popup menu to hold the button, and the MainWindow is not part (parent of) of the popup menu's visual tree, so your ElementName binding can not find the "mainWindow" window, so it assigns null to CommandParameter
You could use static Application.Current to get MainWindow, then it will work.
NB! MainWindow is the Application property name, not the name of your window
<Window x:Class="WpfRelayCommandParameter.MainWindow"
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:wpfRelayCommandParameter="clr-namespace:WpfRelayCommandParameter"
xmlns:ribbon="http://schemas.microsoft.com/winfx/2006/xaml/presentation/ribbon"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance wpfRelayCommandParameter:MainWindowViewModel}"
Title="MainWindow" Height="350" Width="525"
x:Name="mainWindow">
<DockPanel LastChildFill="True">
<DockPanel LastChildFill="True">
<ribbon:Ribbon DockPanel.Dock="Top">
<ribbon:Ribbon.ApplicationMenu>
<ribbon:RibbonApplicationMenu SmallImageSource="Resources/AppMenu.png">
<ribbon:RibbonApplicationMenu.FooterPaneContent>
<ribbon:RibbonButton Label="Beenden"
Command="{Binding CmdCloseApp}"
CommandParameter="{Binding MainWindow, Source={x:Static Application.Current}}"
SmallImageSource="Resources/Exit.png"
HorizontalAlignment="Right" Click="ButtonBase_OnClick"/>
</ribbon:RibbonApplicationMenu.FooterPaneContent>
</ribbon:RibbonApplicationMenu>
</ribbon:Ribbon.ApplicationMenu>
</ribbon:Ribbon>
</DockPanel>
</DockPanel>
Personally I prefer callbacks to the window to go through an interface that can be custom to your view model needs, and can be mocked in unit tests:
<Window x:Class="WpfRelayCommandParameter.MainWindow"
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:wpfRelayCommandParameter="clr-namespace:WpfRelayCommandParameter"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance wpfRelayCommandParameter:MainWindowViewModel}"
Title="MainWindow" Height="350" Width="525"
x:Name="mainWindow">
<Grid>
<Button Content="Close Window"
Command="{Binding CmdCloseApp}"
VerticalAlignment="Top"
HorizontalAlignment="Left" />
</Grid>
Code
public partial class MainWindow : Window, IMainWindow
{
public MainWindow()
{
DataContext = new MainWindowViewModel(this);
InitializeComponent();
}
}
public interface IMainWindow
{
void Close();
}
public class MainWindowViewModel
{
private readonly IMainWindow _mainWindow;
public MainWindowViewModel() : this(null)
{
// Design time
}
public MainWindowViewModel(IMainWindow mainWindow)
{
_mainWindow = mainWindow;
CmdCloseApp = new RelayCommand(CloseApp);
}
public ICommand CmdCloseApp { get; set; }
public void CloseApp(object parameter)
{
_mainWindow.Close();
}
}
Note possible issues with type and null for CanExecute CanExecute on RelayCommand
I'm trying to design the DataTemplate for my ItemsControl and I need some mock data to populate the template. I read using d:DataContext is enough so that I don't have to create a mock class. How can I do this?
The instance you have to use with d:DataContext must be declared in the XAML, with a StaticResource for example.
Here is how you could do it:
<UserControl x:Class="WpfApplication1.UserControl1"
xmlns:local="clr-namespace:WpfApplication1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.Resources>
<local:MyViewModel x:Key="mockViewModel"/>
</UserControl.Resources>
<Grid>
<ItemsControl d:DataContext="{StaticResource mockViewModel}"
ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</UserControl>
The class I used as data context is defined as follows:
namespace WpfApplication1
{
public class Item
{
public Item(string name)
{
Name = name;
}
public string Name { get; private set; }
}
public class MyViewModel
{
public List<Item> Items
{
get
{
return new List<Item>() { new Item("Thing 1"), new Item("Thing 2") };
}
}
}
}
Of course, you can also set the data context on the UserControl or on your Window.
Here's the result:
I have question how to control views in content control. I have two views where are buttons and I want that click in one of this buttons will occur change of view to second. I'm using MVVM and my problem is that I don't know how change ViewModel binded to ContentControl. Maybe my code tell you more than I can clarify:
// Main window view
<Window x:Class="ContentControlTestApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525"
DataContext="{Binding Main, Source={StaticResource Locator}}">
<Grid>
<ContentControl Content="{Binding CurrentViewModel}"/>
</Grid>
</Window>
// Main window view model
public class MainViewModel : ViewModelBase
{
private ViewModelBase _currentViewModel;
public ViewModelBase CurrentViewModel
{
get { return _currentViewModel; }
set
{
_currentViewModel = value;
RaisePropertyChanged("CurrentViewModel");
}
}
private ViewModelLocator Locator
{
get
{
return App.Current.Resources["Locator"] as ViewModelLocator;
}
}
public MainViewModel()
{
CurrentViewModel = Locator.FirstControl;
}
}
//App.xaml
<Application x:Class="ContentControlTestApp.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" StartupUri="MainWindow.xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" d1p1:Ignorable="d" xmlns:d1p1="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:vm="clr-namespace:ContentControlTestApp.ViewModel" xmlns:view="clr-namespace:ContentControlTestApp.View">
<Application.Resources>
<vm:ViewModelLocator x:Key="Locator" d:IsDataSource="True" />
<DataTemplate DataType="{x:Type vm:FirstControlViewModel}">
<view:FirstControlView></view:FirstControlView>
</DataTemplate>
<DataTemplate DataType="{x:Type vm:SecondControlViewModel}">
<view:SecondControlView></view:SecondControlView>
</DataTemplate>
</Application.Resources>
</Application>
//First Control View
<UserControl x:Class="ContentControlTestApp.View.FirstControlView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<StackPanel>
<Label Content="First Control" />
<Button Content="Switch to second control" Command="{Binding SwitchToSecondControlCommand}"/>
</StackPanel>
</Grid>
</UserControl>
//First Control view model
public class FirstControlViewModel:ViewModelBase
{
private RelayCommand _switchToSecondControlCommand;
public ICommand SwitchToSecondControlCommand
{
get
{
return _switchToSecondControlCommand ??
(_switchToSecondControlCommand = new RelayCommand(SwitchToSecondControlExecute));
}
}
private void SwitchToSecondControlExecute()
{
//I don't know what to do here
}
}
//Second Control View
<UserControl x:Class="ContentControlTestApp.View.SecondControlView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<StackPanel>
<Label Content="Second Control" />
<Button Content="Switch to first control" Command="{Binding SwitchToFirstControlCommand}"/>
</StackPanel>
</Grid>
</UserControl>
//Second Control view model
public class SecondControlViewModel:ViewModelBase
{
private RelayCommand _switchToFirstControlCommand;
public ICommand SwitchToFirstControlCommand
{
get
{
return _switchToFirstControlCommand ??
(_switchToFirstControlCommand = new RelayCommand(SwitchToSecondControlExecute));
}
}
private void SwitchToSecondControlExecute()
{
//I don't know what to do here
}
}
//ViewModelLocator
public class ViewModelLocator
{
public ViewModelLocator()
{
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
SimpleIoc.Default.Register<MainViewModel>();
SimpleIoc.Default.Register<FirstControlViewModel>();
SimpleIoc.Default.Register<SecondControlViewModel>();
}
public MainViewModel Main
{
get
{
return ServiceLocator.Current.GetInstance<MainViewModel>();
}
}
public FirstControlViewModel FirstControl
{
get { return ServiceLocator.Current.GetInstance<FirstControlViewModel>(); }
}
public SecondControlViewModel SecondControl
{
get { return ServiceLocator.Current.GetInstance<SecondControlViewModel>(); }
}
public static void Cleanup()
{
// TODO Clear the ViewModels
}
}
And I don't know how change CurrentViewModel in MainViewModel from for example FirstControlViewModel's command. Any idea? I thought about some event but this looks not good. Does anyone have any idea?
Thanks
First things first... to change the view, we change the view model (assuming that you have correctly declared a DataTemplate for it):
<ContentControl Content="{Binding CurrentViewModel}"/>
...
CurrentViewModel = new OtherViewModel();
Clearly, you can only do that from your MainViewModel. Therefore, you should handle the Button ICommand in your MainViewModel, so move your SwitchToFirstControlCommand there and change your Button.Command Binding Path to this:
<Button Content="Switch to first control" Command="{Binding DataContext.
SwitchToFirstControlCommand, RelativeSource={RelativeSource
AncestorType={x:Type MainWindow}}}" />
Now in your main view model:
private void SwitchToSecondControlExecute()
{
CurrentViewModel = new OtherViewModel();
}
I got some problem with custom dependency property binding.
We have:
Custom user control with one dependency property and binding to self:
<UserControl x:Class="WpfApplication1.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"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300" DataContext="{Binding RelativeSource={RelativeSource Mode=Self}}">
<Grid>
<Label>
<Label.Template>
<ControlTemplate>
<Label Content="{Binding MyTest}"/>
</ControlTemplate>
</Label.Template>
</Label>
</Grid>
... and code of control:
public partial class SomeUserControl : UserControl
{
public SomeUserControl()
{
InitializeComponent();
}
public static readonly DependencyProperty MyTestProperty = DependencyProperty.Register("MyTest", typeof(int), typeof(SomeUserControl));
public int MyTest
{
get { return (int)GetValue(MyTestProperty); }
set { SetValue(MyTestProperty, value); }
}
}
I trying to use this control with binding to some simple property of simple model class:
<UserControl x:Class="WpfApplication1.AnotherUserControl"
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:wpfApplication1="clr-namespace:WpfApplication1"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300" DataContext="{Binding RelativeSource={RelativeSource Mode=Self}}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<wpfApplication1:SomeUserControl MyTest="{Binding Path=Model.MyNum}" Grid.Column="0"/>
<Label Content="{Binding Path=Model.MyNum}" Grid.Column="1"/>
</Grid>
... with code
public partial class AnotherUserControl : UserControl
{
public MyModel Model { get; set; }
public AnotherUserControl()
{
Model = new MyModel();
Model.MyNum = 1231;
InitializeComponent();
}
}
... and model:
public class MyModel:INotifyPropertyChanged
{
private int _myNum;
public int MyNum
{
get { return _myNum; }
set { _myNum = value; OnPropertyChanged("MyNum");}
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
But binding is not working. Stack have no errors in compilation. Now, binding working with statndart wpf label control (with same model), and don't working with my custom control and custom property. Please help me to understand reasons of this problem and solve it;
Thanks)
You should use ElementName Binding in your SomeUserControl.
<UserControl x:Class="WpfApplication1.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"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
x:Name="uc">
<Grid>
<Label>
<Label.Template>
<ControlTemplate>
<Label Content="{Binding MyTest, ElementName=uc}"/>
</ControlTemplate>
</Label.Template>
</Label>
</Grid>
here some more information why you should not set the datacontext of a usercontrol to self/this.
Data Binding in WPF User Controls
You will need to update your binding as below i.e you will have to bind to the ancestor usercontrol property using RelativeSource as you are setting the DataContext of child usercontrol explicitly, default binding will search for path inside the child userControl's DataContext.
<wpfApplication1:SomeUserControl MyTest="{Binding Path=DataContext.Model.MyNum, RelativeSource={RelativeSource FindAncestor={x:Type UserControl}}}" Grid.Column="0"/>