I have a UserControl called LoginControl where I have defined a Command:
//This is LoginControl
public ICommand LoginCommand
{
get { return (ICommand)GetValue(LoginCommandProperty); }
set { SetValue(LoginCommandProperty, value); }
}
public static readonly DependencyProperty LoginCommandProperty =
DependencyProperty.Register("LoginCommand",
typeof(ICommand),
typeof(LoginControl));
I have a button in the LoginControl where the Click event calls this event handler:
private void Login_Click(object sender, RoutedEventArgs e)
{
LoginCommand.Execute(passwordBox.Password);
}
Now I have another UserControl called SettingsControl where I have included the LoginControl:
<local:LoginControl Grid.Row="1" Margin="5" LoginCommand="{Binding MyCommand}"/>
The DataContext of the SettingsControl is set to itself : DataContext="{Binding RelativeSource={RelativeSource Self}}
SettingsControl is defined like this:
public partial class SettingsControl : UserControl
{
public SettingsControl()
{
InitializeComponent();
MyCommand = new RelayCommand(o => MessageBox.Show("YESSS!"));
}
public ICommand MyCommand { get; set; }
}
When the Login_Click event handler is called, the code throws a NullReferenceException (MyCommand is null). I don't understand why. The MyCommand is initialised as you can see here. When I am initialising the LoginControl, I pass the MyCommand to it. So I don't understand why it should be null.
After like 2 hours thinking, I have found the solution.
Move MyCommand initialisation above InitializeComponent (Thanks to #Spawn for the suggestion)
Give a name to SettingsControl in xaml. Then change the LoginCommand binding as follows:
{Binding MyCommand, ElementName=settingControl}
And this worked. I am guessing the previous binding was looking for the DataContext of the LoginControl itself. By specifying the ElementName, wpf knows what DataContext I wanted (i.e. the DataContext of the SettingsControl).
Related
I am having a UserControl called ChartControl which displays a chart. To determine that the ChartControl is initialized, it needs to run a command which is defined inside a ViewModel called DiagnosisViewModel. The Command gets instantiated inside this ViewModel. After the ChartControl is loaded, it should execute the Command, but at this point, it is still null. It seems like that the Binding of the Command does not work as expected. Let the explain it better with code:
ChartControl.xaml
<UserControl x:Class="GoBeyond.Ui.Controls.Charts.ChartControl"
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"
x:Name="ThisChartControl"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
</UserControl>
ChartControl.xaml.cs
public partial class ChartControl : INotifyPropertyChanged
{
public ChartControl()
{
Loaded += OnLoaded;
}
public static readonly DependencyProperty ReadyCommandProperty = DependencyProperty.Register(
nameof(ReadyCommand), typeof(ICommand), typeof(ChartControl), new PropertyMetadata((o, args) =>
{
Debug.WriteLine("Ready Command updated");
}));
public ICommand ReadyCommand
{
get { return (ICommand)GetValue(ReadyCommandProperty); }
set { SetValue(ReadyCommandProperty, value); }
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
// Initialize the ChartControl with some values
...
// At this point the Command is ALWAYS null
ReadyCommand?.Execute(this);
}
}
ChartControl defined in DiagnosisView.xaml
<charts:ChartControl ReadyCommand="{Binding FrequencyReadyCommand}" />
DiagnosisViewModel
public class DiagnosisViewModel : ViewModelBase // ViewModelBase derives from Prisms BindableBase
{
public DiagnosisViewModel(...)
{
FrequencyReadyCommand = new DelegateCommand<ChartControl>(OnFrequencyChartReady);
}
public ICommand FrequencyReadyCommand
{
get => _frequencyReadyCommand;
set => SetProperty(ref _frequencyReadyCommand, value);
}
}
The getter of FrequencyReadyCommand seems never gets called, so I think there is any issue with my binding here. I tried with multiple Modes and UpdateSourceTriggers, but I cannot find out, what I am doing wrong here
You should invoke the command in the dependency property callback instead of handling the Loaded event:
public static readonly DependencyProperty ReadyCommandProperty = DependencyProperty.Register(
nameof(ReadyCommand), typeof(ICommand), typeof(ChartControl), new PropertyMetadata((o, args) =>
{
ChartControl chartControl = (ChartControl)o;
chartControl.ReadyCommand?.Execute(null);
}));
Unlike the Loaded event, the callback is always invoked after the property has been set.
I have an application in which I set the content of a contentpresenter, dependent on the datatype by a datatemplate (see MainWindow). The Datatemplate is a usercontrol, which is actually datatype specific. (The small example below is only for demonstration, but in my "real" application the user shall be able to switch between different data.)
The usercontrol (UserControl1) has a DependencyProperty which I assign a value (in my application this is actually a binding to a VM, just set it to a string in example for simplicity).
Setting the value is still working fine. However In my UserControl I need to react to changes of the DependencyProperty to change the view of my UserControl (or later on CustomControl). So I implemented a OnPropertyChangend method.
When application starts OnPropertyChanged works as I expect it and I get the "correct" newvalue of my DependencyProperty. However, if I change my VM (i.e. my datatemplate changes) during runtime by clicking on a button, OnPropertyChanged returns the DependencyProperty's defaultvalue.
In my small example application, I can see that the value is set correctly, as the Textblock content changes to the correct value.
It only seems that OnPropertyChanged gets fired before my DependencyProperty's value gets the new value. So, it's not possible for me to react on the new value.
It is not really clear why this happens. Seems to have something to do with the order in which WPF resolves internal stuff?
Does anyone have a clue, how I can fix this behavior and get access to the current/last value when changing my VM and don't miss an update? As stated out before, I need to react on that value.
Maybe I am doing something totally stupid here. Is the approach I decided to use here a bad one? Are DataTemplates the wrong approach to switch between two pairs? What would be a better approach then? However, I guess it won't be possible to avoid the DependencyProperty and the UserControl in my application.
MainWindow.xaml
<!--MainWindow.xaml -->
<Grid>
<StackPanel>
<Button Content="Button" HorizontalAlignment="Left" VerticalAlignment="Top" Width="75" Click="Button_Click"/>
<ContentPresenter Content="{Binding ActiveVM}">
<ContentPresenter.Resources>
<DataTemplate DataType="{x:Type local:VM1}">
<local:UserControl1 MyProperty="Test1"/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:VM2}">
<local:UserControl1 MyProperty="Test2"/>
</DataTemplate>
</ContentPresenter.Resources>
</ContentPresenter>
</StackPanel>
</Grid>
MainWindow.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
vmParent = new VMParent();
DataContext = vmParent;
var vm1 = new VM1();
var vm2 = new VM2();
}
VMParent vmParent;
private void Button_Click(object sender, RoutedEventArgs e)
{
vmParent.ChangeActiveVM();
}
}
UserControl1.xaml
<!--UserControl1.xaml -->
<TextBlock Text="{Binding MyProperty, RelativeSource={RelativeSource AncestorType={x:Type local:UserControl1}}}"/>
UserControl1.cs
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
}
public string MyProperty
{
get { return (string)GetValue(MyPropertyProperty); }
set { SetValue(MyPropertyProperty, value); }
}
public static readonly DependencyProperty MyPropertyProperty =
DependencyProperty.Register("MyProperty", typeof(string), typeof(UserControl1), new PropertyMetadata("DefaultString", OnMyPropertyChangend));
private static void OnMyPropertyChangend(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue == "DefaultString")
{
;
//xxxxxx
//unexpectedly i get stuck here
//Would expect/need NewValue to be Text1/Text2 to react to it
//xxxxxx
}
}
}
VMParent
class VMParent : INotifyPropertyChanged
{
public VMParent()
{
vm1 = new VM1();
vm2 = new VM2();
ActiveVM = vm1;
}
public event PropertyChangedEventHandler PropertyChanged;
VM1 vm1;
VM2 vm2;
public object ActiveVM
{
get => m_activeVM;
set { m_activeVM = value; OnPropertyChanged("ActiveVM"); }
}
private object m_activeVM;
protected internal void OnPropertyChanged(string propertyname)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyname));
}
public void ChangeActiveVM()
{
if (ActiveVM is VM1)
ActiveVM = vm2;
else
ActiveVM = vm1;
}
}
VMs are only used to apply Datatemplate
class VM1
{
}
class VM2
{
}
I've made a user control which contains a command, to be called in response to a certain event. This command is a dependency property. I want to use it in the main window like this:
<local:myUserControl Command="{Binding someCommand}"/>
The "myCommand" is the dependency property I created for this user control. And I bind it to a command of the view model of the main window ("someCommand").
The problem is that I am setting the datacontext of my usercontrol (I have a view model for it), and it seems to reset the "Command" to null… Here is the code-behind of my view model:
public partial class myUserControl : UserControl, ICommandSource
{
public myUserControl()
{
this.DataContext = new myViewModel();
InitializeComponent();
}
public ICommand Command
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
public static readonly DependencyProperty CommandProperty =
DependencyProperty.Register("Command", typeof(ICommand), typeof(myUserControl), new PropertyMetadata(null));
public object CommandParameter
{
get { return (object)GetValue(CommandParameterProperty); }
set { SetValue(CommandParameterProperty, value); }
}
public static readonly DependencyProperty CommandParameterProperty =
DependencyProperty.Register("CommandParameter", typeof(object), typeof(myUserControl), new PropertyMetadata(0));
public IInputElement CommandTarget
{
get { return (IInputElement)GetValue(CommandTargetProperty); }
set { SetValue(CommandTargetProperty, value); }
}
public static readonly DependencyProperty CommandTargetProperty =
DependencyProperty.Register("CommandTarget", typeof(IInputElement), typeof(myUserControl), new PropertyMetadata(null));
private void TextBlock_MouseUp(object sender, MouseButtonEventArgs e)
{
Command.Execute(this.CommandParameter);
}
}
The code of my user control could be the Following:
<UserControl x:Class="myApp.myUserControl"
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:myApp"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<TextBlock MouseUp="TextBlock_MouseUp">
</TextBlock>
</Grid>
</UserControl>
(I know that this element seems a bit silly (or useless), but I have simplified it to test what didn't worked and also in order to ask a rather simple question).
I have discovered that, if I comment the "this.DataContext = new myViewModel();" line, the binding to the command works perfectly. And when I uncomment this line and put a breakpoint in the "TextBlock_MouseUp", the "Command" property is equal to null...
Would there be a way to resolve this problem? I have some complicated code in my view model (so I'm quite forced to keep this line "this.DataContext = new myViewModel();"), and I am not sure I could find another solution than having a "Command" dependency property in my user control…
To be sure I give a maximum of informations, I have the following code in the view model of my main window:
public ICommand someCommand { get; set; }
//Constructor
public MainWindowViewModel()
{
this.someCommand = new RelayCommand((obj) => { return true; },
(obj) =>
{
//I put a breakpoint here
int dummy = 0;
});
}
(The RelayCommand class is a standard RelayCommand class, with a "Predicate" CanExecute and an "Action Execute).
I hope this question is not a duplicate… I have found several similar question, but they did not seem to answer mine...
I'm really sorry for this question which was in fact a bit silly. I hadn't understand very well what happens during a binding. I thought that this code line in the MainWindow…
<local:myUserControl Command="{Binding someCommand}"/>
…would have made an attempt to bind the UserControl's "Command" property to the "someCommand" of the datacontext of the MainWindow. In fact, as #elgonzo pointed out, the binding looks up in the UserControl's datacontext for the "someCommand" property (and not in the MainWindow's datacontext!!). Therefore, setting the UserControl's datacontext with this line…
this.DataContext = new myViewModel();
...was preventing the binding to be correctly done (since it looks for the "someCommand" property of the UserControl's datacontext, which is now "myViewModel", which does not contain "someCommand"...).
To fix this, I had to change the binding like this:
<local:myUserControl Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}},
Path=DataContext.someCommand}"/>
I've found this solution here: https://stackoverflow.com/a/1127964/11609068.
Maybe it is not the best way to do it (the "Path= DataContext. someCommand" make me think this, it doesn't seem very elegant), but it works. Another way to do it is to name the MainWindow (x:Name="someName"), so that the binding is a bit simpler:
<local:myUserControl Command="{Binding ElementName=someName, Path=DataContext.someCommand}"/>
Again, sorry and many thanks to #elgonzo.
I've been working with the MVVM model for a week or so now and I think I have a handle on what should go where now. Note the "think" in that.
I have a single ViewModel that my view (MainWindow) binds to
_ViewModel = new MainViewModel();
this.DataContext = _ViewModel;
I have a few ICommands that do work within the ViewModel and subsequently the Model, which I'm fine with.
Now I initiate a few windows from my View (MainWindow) which I do in codebehind, as it's all purely view related stuff. I am trying to replicate the ICommand setup I have in the ViewModel in the View to simplify my life, or so I thought. I have the following commands set-up:
public ICommand comInitialiseWindows { get; private set; }
private bool _windowsactive = false;
public bool WindowsActive
{
get { return _windowsactive; }
set { SetProperty(ref _windowsactive, value); }
}
public bool comInitialiseWindows_CAN()
{
return !_windowsactive;
}
private void comInitialiseWindows_DO()
{
... Code to do the window creation, etc.
}
I have this relay command in the MainWindow code:
comInitialiseWindows = new RelayCommand(() => comInitialiseWindows_DO(), comInitialiseWindows_CAN);
If I put this in the ViewModel it works a treat apart from the window creation stuff, but as it's View related I'm not surprised.
So the problem is the code doesn't run when I click the button. I'm guessing that the XAML is bound to the ViewModel, but I can't figure a way around this without setting the Binding for each button to the MainWindow in codebehind. I had assumed that the following would work, but it doesn't:
<Button x:Name="ribbutLayoutWindows"
Command="{Binding local:comInitialiseWindows}"
IsEnabled="{Binding local:comInitialiseWindows_CAN}"/>
I'm pretty sure I'm just not getting something somewhere. Or I'm trying to overcomplicate matters where a normal button click would have sufficed as it's View only.
Any suggestions?
There are two possibilities:
Through the ViewModel:
You could expose a Property on your ViewModel:
class MainViewModel
{
ICommand comInitialiseWindows {get; set;}
}
And in your MainWindow:
MainViewModel vm = this.DataContext as MainViewModel;
vm.comInitialiseWindows = new RelayCommand(() => comInitialiseWindows_DO(), comInitialiseWindows_CAN);
XAML:
<Button x:Name="ribbutLayoutWindows" Command="{Binding comInitialiseWindows}" />
Note: you don't need to bind the IsEnabled property. WPF will handle that for you and automatically call into the CanExecute-method of your ICommand.
Through a DependencyProperty
Declare this dependecyProperty in your code-behind:
public ICommand comInitialiseWindows
{
get { return (ICommand)GetValue(MyPropertyProperty); }
set { SetValue(MyPropertyProperty, value); }
}
public static readonly DependencyProperty comInitialiseWindowsProperty =
DependencyProperty.Register("comInitialiseWindows", typeof(ICommand), typeof(MainWindow), new PropertyMetadata(null));
Assign a value in the code-behind:
comInitialiseWindows = new RelayCommand(() => comInitialiseWindows_DO(), comInitialiseWindows_CAN);
After that, you need to break out of your data-context in the XAML. First of all, give your Page a name:
<Window x:Class="Web_Media_Seeker_WPF.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:local="clr-namespace:Web_Media_Seeker_WPF"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Name="myWindow"
Title="MainWindow" Height="350" Width="525">
And then declare your binding as follows:
<Button x:Name="ribbutLayoutWindows" Command="{Binding comInitialiseWindows, ElementName=myWindow}" />
In my MainWindow I create a new instance of a class containing different settings. After setting the parameters of the class, I set the datacontext = to that class.
public partial class MainWindow : Window
{
private MeasConSettings mMeasConSettings = new MeasConSettings();
public MainWindow()
{
InitializeComponent();
DataContext = mMeasConSettings;
}
private void MenuComm_Click(object sender, RoutedEventArgs e)
{// See code below}
}
Now I also have a function to open a new window, this window contains a textbox who's text should be bound to the datacontext of the MainWindow.
private void MenuComm_Click(object sender, RoutedEventArgs e)
{
FrmSettings newWindow = new FrmSettings();
newWindow.DataContext = mMeasConSettings;
newWindow.TxtComm.Text = mMeasConSettings.CommSettings;
newWindow.Show();
}
This code fills in the textbox from the newWindow with the right content, BUT it does not get bound propery since the datacontext does not get updated after changing the text in the textbox (TxtComm in the new created window).
An example of the XAML code for the textbox:
<TextBox Grid.Row="1" Grid.Column="3" Margin="2,0" Name="TxtComm" DataContext="{Binding Path=CommSettings, UpdateSourceTrigger=PropertyChanged}" />
"CommSettings" is a member of the MeasConsettings class
public class MeasConSettings
{
private string mCommSettings;
public string CommSettings
{
get
{
return mCommSettings;
}
set
{
mCommSettings = value;
}
}
public MeasConSettings()
{
CommSettings = "Com5:19200,8,n,1";
}
}
My problem is how can I adjust the value mMeasConSettings.CommSettings (defined in my MainWindow) in my newWindow (Which is created after pressing a button), If I change the textbox value in my newWindow, the value stored in mMeasConSettings.CommSettings should also be changed.
PS: I'm new to WPF so any advice is welcome!
As I wrote in the comment, you need to bind the Text property of your TextBox to the property of the DataContext which you want to update. Your XAML should thus be something like:
<TextBox ... Text="{Binding CommSettings, Mode=TwoWay}" />
Note that I am binding the Text property of the TextBox to the property CommSettings of your DataContext. And your C#-code for the click event should be:
private void MenuComm_Click(object sender, RoutedEventArgs e)
{
FrmSettings newWindow = new FrmSettings();
newWindow.DataContext = mMeasConSettings;
newWindow.Show();
}
We only need to set the DataContext here. Note that the DataContext is passed along to child elements, so the TextBox will have the same DataContext as its parent unless specifically set to something else.
use static property:
class Demo
{
public static string SomeSettings {get;set;}
private onLoad()
{
SomeSettings=... //Init here
}
}
In other file:
Demo.SomeSettings=....