Cant get a wpf textbox to bind to my player object - c#

I have been trying to bind my Player object with my UI.
If the tutorials are correct I do not see why it shouldn't work.
I have my Main window that create an UserControl inside a tabPage when a button is pressed. This user control contains my player. In the main window I will pass a player to the user control.
Here is my main window:
public partial class MainWindow : INotifyPropertyChanged
{
public MainWindow()
{
DataContext = this;
InitializeComponent();
}
private void AddPlayerClick(Object sender, RoutedEventArgs e)
{
AddTabItem("New Player", new AddPlayer(new Player(1, "asd", "asd", new DateTime(1,2,3), "asd", "asd", "asd", "asd", true)));
}
public void AddTabItem(String name, UserControl userControl)
{
TabItem tab = new TabItem
{
Header = name
};
userControl.DataContext = userControl;
tab.Content = userControl;
TabControl.Items.Add(tab);
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] String propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
<Window x:Class="Tournament_App.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:Tournament_App"
mc:Ignorable="d"
Title="MainWindow" Height="1920" Width="1080">
<StackPanel>
<Menu>
<MenuItem Header="Players">
<MenuItem Header="Add Player" Click="AddPlayerClick"/>
</MenuItem>
</Menu>
</StackPanel>
</Window>
When the button is clicked a new tab is created and a player gets passed through.
Then in the AddPlayer User Control it set to a private property with a public get/setter.
public partial class AddPlayer : UserControl, INotifyPropertyChanged
{
private Player _player;
public Player Player
{
get { return _player; }
set {
_player = value;
OnPropertyChanged();
}
}
public AddPlayer(Player player)
{
DataContext = Player;
InitializeComponent();
Player = player;
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] String propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
I then bind the players first name to a textbox.
<UserControl x:Class="Tournament_App.Views.AddPlayer"
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:Tournament_App.Views"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800" >
<StackPanel>
<Label Content="First Name" />
<TextBox Text="{Binding FirstName, Mode=TwoWay}"/>
</StackPanel>
As far as i can tell the data context is right. And the binding names are all correct, so I dont get why it is not working. Any help would be apreciated. I belive I have too many INotifyProperty changed, but I cant quite figuire out where I need them. As you might be able to tell I am pretty new to Wpf.

First you are setting the AddPlayer.DataContext to the uninitialized property Player inside the constructor of AddPlayer:
public AddPlayer(Player player)
{
DataContext = Player; // Wrong! Property is not initialized.
InitializeComponent(); // Wrong! InitializeComponent() should be the very first call
Player = player;
}
This might be a typo. Furthermore InitializeComponent() should always be the very first call.
But after instantiating AddPlayer you override the DataContext by setting it to theAddPlayer` itself:
public void AddTabItem(String name, UserControl userControl)
{
TabItem tab = new TabItem
{
Header = name
};
userControl.DataContext = userControl; // Wrong! This overrides the constructor assignment.
tab.Content = userControl;
TabControl.Items.Add(tab);
}
Instead ad the items to the TabControl.ItemsSource and let the control handle the DataContext. Also never implement INotifyPropertyChanged on controls (or DependencyObject. Always implement DependencyProperty as this has much better perfomance. It's also very likely that the control's properties are the Binding.Target and bound to a data source. Binding.Target must be a DependencyProperty:
TabItemData.cs
public class TabItemData
{
public TabItemData(string title, Player player)
{
this.Title = title;
this.Player = player;
}
public Player Player { get; set; }
public string Title { get; set; }
}
AddPlayer.xaml.cs
public partial class AddPlayer : UserControl, INotifyPropertyChanged
{
public AddPlayer()
{
InitializeComponent();
// Player is already the DataContext, set from XAML DataTemplate.
// Access player like "var player = this.DataContext as Player;"
// This instance is automatically created by a DataTemplate
}
}
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public static readonly DependencyProperty PlayersProperty = DependencyProperty.Register(
"Players",
typeof(ObservableCollection<TabItemData>),
typeof(MainWindow),
new PropertyMetadata(default(ObservableCollection<TabItemData>)));
public ObservableCollection<TabItemData> Players
{
get => (ObservableCollection<TabItemData>) GetValue(MainWindow.PlayersProperty);
set => SetValue(MainWindow.PlayersProperty, value);
}
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
this.Players = new ObservableCollection<TabItemData>();
}
private void AddPlayerClick(Object sender, RoutedEventArgs e)
{
this.Players.Add(new TabItemData("New Player", new Player(1, "asd", "asd", new DateTime(1,2,3), "asd", "asd", "asd", "asd", true)));
}
}
MainWiindow.xaml
<Window x:Name="Window">
<StackPanel>
<Menu>
<MenuItem Header="Players">
<MenuItem Header="Add Player" Click="AddPlayerClick" />
</MenuItem>
</Menu>
<TabControl ItemsSource="{Binding ElementName=Window, Path=Players>
<TabControl.ItemTemplate>
<DataTemplate DataType="{x:Type TabItemData}">
<TextBlock Text="{Binding Title}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate DataType="{x:Type TabItemData}">
<AddPlayer DataContext="{Binding Player}" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</StackPanel>
</Window>

Related

WPF Binding and Observable Model fail

In a simple trying-to-learn-WPF experiment I'm trying to bind a property ("InternalName") of an instance of MyModel to the contents of TextBlock "MainWindowTextBlock". Clicking the ``ChangeNameButton" changes the InternalName property of mymodel, but that property change never makes it through to the TextBlock. Nothing happens. What am I doing wrong?
XMAL
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:UserControlExperiments"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<StackPanel Grid.Row ="0">
<Button Width="100" Height="20" Name="ChangeName" Content="Change the Name" Click="ChangeNameButtonClick"/>
<TextBlock Text=""/>
<TextBlock Name="MainWindowTextBox" Width="100" Height="20" Text="{Binding Path = mymodel.InternalName, Mode=TwoWay}"/>
</StackPanel>
</Grid>
</Window>
CODE BEHIND
public partial class MainWindow : Window
{
public MyModel mymodel;
public MainWindow()
{
InitializeComponent();
DataContext = this.DataContext;
mymodel = new MyModel("The old name");
}
private void ChangeNameButtonClick(object sender, RoutedEventArgs e)
{
mymodel.InternalName = "A new name!";
}
}
public class MyModel : INotifyPropertyChanged
{
private string internalname;
public event PropertyChangedEventHandler PropertyChanged;
public MyModel(string nm)
{
InternalName = nm;
}
protected void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public string InternalName
{
get { return internalname; }
set
{
if (internalname != value)
{
internalname = value;
OnPropertyChanged("InternalName");
}
}
}
}
}
The following markup tries to bind to a property named "mymodel" of the current DataContext of the TextBlock, which is inherited from the parent window:
<TextBlock Name="MainWindowTextBox"
Text="{Binding Path = mymodel.InternalName}"/>
So you need to set the DataContext of the window to itself:
DataContext = this;
And you also need to make mymodel a public property since you cannot bind to fields:
public MyModel mymodel { get; }
Then it should work but you probably also want to change the name of the property to comply with the C# naming standards.
You can also remove Mode=TwoWay from the binding. It makes no sense for a TextBlock.

UserControl with Collection Binding always returning Null

Problem: When attempting to bind an Observable Collection to my user control, it is always showing null at runtime.
Description: I have a user control as described below. The goal is to create a button to cycles through an array of images with every click. However, when I run this, ImageCollection is always null, regardless of how I setup the binding on the implementation side. I'm at a loss for why this is. The code is as follows:
XAML:
<UserControl x:Class="kTrack.ToggleImage"
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:kTrack"
mc:Ignorable="d"
d:DesignHeight="{Binding ImageHeight}" d:DesignWidth="{Binding ImageWidth}">
<Grid x:Name="LayoutRoot">
<Button Click="ToggleImage_Click" Height="{Binding ImageHeight}" Width="{Binding ImageWidth}">
<Image Source="{Binding ActiveImage}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" ToolTip="{Binding ToolTip}" />
</Button>
</Grid>
</UserControl>
Code-Behind (Important Bits)
public partial class ToggleImage : UserControl, INotifyPropertyChanged
{
public static readonly DependencyProperty ImageCollectionProperty = DependencyProperty.Register("ImageCollection", typeof(ObservableCollection<string>), typeof(ToggleImage), new PropertyMetadata(null));
...
public ObservableCollection<String> ImageCollection
{
get => (ObservableCollection<String>)GetValue(ImageCollectionProperty);
set => SetValue(ImageCollectionProperty, value);
}
private ImageSource _activeImage;
public ImageSource ActiveImage
{
get => _activeImage;
set
{
_activeImage = value;
OnPropertyChanged();
}
}
private int _currentImageIndex;
public int CurrentImageIndex
{
get => _currentImageIndex;
set
{
_currentImageIndex = value;
OnPropertyChanged();
}
}
// Constructor
public ToggleImage()
{
ImageCollection = new ObservableCollection<String>();
InitializeComponent();
LayoutRoot.DataContext = this;
CurrentImageIndex = 0;
if (ImageCollection.Count > 0)
{
ActiveImage = SetCurrentImage(CurrentImageIndex);
}
}
...
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
XAML Implementation:
<local:ToggleImage ImageHeight="32" ImageWidth="32"
ImageCollection="{Binding Images, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" />
Code-Behind of Implementation:
public partial class MainWindow : kWindow
{
public ObservableCollection<String> Images = new ObservableCollection<String>()
{
"pack://application:,,,/Resources/Icons/Close.png",
"pack://application:,,,/Resources/Icons/Minimize.png",
"pack://application:,,,/Resources/Icons/WindowOne.png"
};
public MainWindow()
{
InitializeComponent();
}
}
for one, you should set DataContext in Window.
for two, Binding work with properties, not fields: change Images field to property.
public partial class MainWindow : kWindow
{
public ObservableCollection<String> Images { get; } = new ObservableCollection<String>()
{
"pack://application:,,,/Resources/Icons/Close.png",
"pack://application:,,,/Resources/Icons/Minimize.png",
"pack://application:,,,/Resources/Icons/WindowOne.png"
};
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
}

How to instance a child Window in WPF that takes parameters from the main Window, but respects MVVM

I have implemented something violating the MVVM pattern, and I wondered if there was a MVVM way of doing this.
I have a Window MainWindow, its DataContext is bound to a class called ViewModel which implements INotifyPropertyChanged.
I also implemented a Window ChildWindow which appears in a "Dialog" style when a button is clicked, using a RelayCommand. The DataContext of ChildWindow also binds to ViewModel. This Window is used to fill the details of a new list Item. I pass the View as a CommandParameter to the ViewModel, so that the ChildWindow can be centered in comparison to the MainWindow. This is not MVVM, and I would like to change this.
First, I implemented this in a non-MVVM way:
Here is my XAML for the button in MainWindow which opens the ChildWindow:
<Button Name="BtnInsert" Width="50" Margin="10" Command="{Binding OpenChildWindowCommand}" CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}">Add</Button>
Here is my simplified XAML for the ChildWindow:
<Window x:Class="HWE_Einteilen_Prototype.View.ListItemWindow"
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:HWE_Einteilen_Prototype.View"
mc:Ignorable="d"
Title="test" Height="400" Width="400">
<TextBox Width="50" Text="{Binding CurrentListItem.Id}" ></TextBox>
</Window>
And here is my (simplified) ViewModel Class:
public class ViewModel : INotifyPropertyChanged
{
private DataContext _ctx;
private ListItem _currentListItem;
private ObservableCollection<listItem> _listItems;
private ListItemWindow _listItemWindow;
private ICommand _openListItemWindowCommand;
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<ListItem> ListItems
{
get { return _listItems; }
set
{
_listItems = value;
OnPropertyChanged();
}
}
public ListItem CurrentListItem
{
get { return _currentListItem; }
set
{
_currentListItem = value;
OnPropertyChanged();
}
}
public ICommand OpenListItemWindowCommand
{
get { return _openListItemWindowCommand; }
set
{
_openListItemWindowCommand = value;
OnPropertyChanged();
}
}
public ViewModel()
{
OpenListItemWindowCommand = new RelayCommand(this.OpenNewListItemWindow, this.CanOpenListItemWindow);
}
private void OpenNewListItemWindow(object parameter)
{
CurrentListItem = new listItem(){Id = "testId"};
_listItemWindow = new StListItemWindow(){DataContext = this};
_listItemWindow.Owner = (MainWindow)parameter;
_listItemWindow.WindowStartupLocation = WindowStartupLocation.CenterOwner;
_listItemWindow.Closing += OnStListItemWindowClosing;
_listItemWindow.Show();
}
private bool CanOpenListItemWindow(object parameter)
{
return true;
}
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
What I have tried:
I have tried implementing a Behavior (from system.windows.interactivity) for the button opening the child window, so that it creates a new Window and does all the centering and owner stuff, and leaving only CurrentListItem = new listItem(){Id = "testId"}; in the command method. However, in this case binding to CurrentListItem in the ChildWindow throws an exception.
XAML Code for the MainWindow Button:
<Button Name="BtnInsert" Width="50" Margin="10" Command="{Binding OpenListItemWindowCommand}" Content="Add">
<i:Interaction.Behaviors>
<behaviors:BehButtonNewWindow></behaviors:BehButtonNewWindow>
</i:Interaction.Behaviors>
</Button>
Behavior Code:
class BehButtonNewWindow : Behavior<Button>
{
private StListItemWindow _ListItemWindow;
protected override void OnAttached()
{
AssociatedObject.Click += OnClickHandler;
}
protected override void OnDetaching()
{
AssociatedObject.Click -= OnClickHandler;
}
private void OnClickHandler(object sender, RoutedEventArgs routedEventArgs)
{
if (sender is Button button)
{
var win = Window.GetWindow(button);
if (win != null)
{
_ListItemWindow = new ListItemWindow
{
DataContext = win.DataContext,
Owner = win,
WindowStartupLocation = WindowStartupLocation.CenterOwner
};
_ListItemWindow.Show();
}
}
}
}
Code of Command Execute Method from ViewModel:
private void OpenNewStListItemWindow(object parameter)
{
CurrentListItem = new ListItem(){Id = "testId"};
}
What am I doing wrong?
Credit for this answer goes to Will (see comments)
On handling the window opening:
Opening a window is a UI concern. Simply handle the button click in the codebehind, construct a new window and stick the current VM in it. MVVM != no codebehind.
On handling vm code:
[...] If you mean that last little bit of code at the bottom, make it public and have the window call it before opening the new window. The UI is perfectly fine knowing about your view models. They're designed to display their state and bind to their properties.
Thanks for your help!

How to excecute a command declared in child viewmodel in an MVVM app?

I've got a MainWindowVM and multiple child viewmodels inheriting from it.
MainWindowVM inherits from ViewModelBase which implements INotifyPropertychanged.
Each view has DataContext set to CurrentViewModel defined in MainWindowVM and every button
has got a binding to a command.
If I put the commands (and other command-handling code in the constructor) in the MainWindowVM,
button clicks in every view works as expected. I set MainControlVM as CurrentViewModel in the constructor of MainWindowVM.
Except for MainControlVM and MainWindowVM, setting commands in any other VM means they wont execute.
However, I want to have commands only in the VMs they are used.
I found many tutorials on MVVM with only one or two viewmodels so this situation isnt an issue for them.
Edit including code:
This is the relevant code:
Part of one of the child views in XAML with a binding:
<Grid DataContext="{Binding CurrentViewModel}" Margin="0,0,-186,0">
<Button Content="Add" HorizontalAlignment="Left" Margin="25,249,0,0" VerticalAlignment="Top" Width="62" Height="32"
Command="{Binding AddCategoryVMCommand}" />
MainWindowVM class contains:
public ICommand AddCategoryVMCommand { get; private set; }
and, in the constructor:
AddCategoryVMCommand = new RelayCommand(() => ExecuteAddCategoryVMCommand());
and:
protected void ExecuteAddCategoryVMCommand()
{
CurrentViewModel = new AddCategoryVM();
}
....and the same kind of code for each command. Aso, CurrentViewModel is set in the MainWindowVM class. This is the property that the MainWindow view uses to determine which view to display along with a datatemplate:
public ViewModelBase CurrentViewModel
{
get { return _currentViewModel; }
set
{
if (_currentViewModel == value)
return;
_currentViewModel = value;
this.RaiseNotifyPropertyChanged("CurrentViewModel");
}
}
How can I make commands execute when declared in child viewmodel?
There are a lot of comments going on, all out of sync and they appear to convolute the issue so I thought I would try to solve your problem with a basic example. The example deals solely with the command binding issue you appear to have.
I have created 3 ViewModel's, MyViewModel1 and MyViewModel2 are derived of MyViewModel. There is a command defined in the base ViewModel which is used to load the CurrentViewModel. The other 2 ViewModels contain their own commands.
public class MyViewModel : INotifyPropertyChanged
{
private MyViewModel currentViewModel;
public RelayCommand<object> MyCommand { get; set; }
public MyViewModel()
{
MyCommand = new RelayCommand<object>(MyCommandExecute);
}
public MyViewModel CurrentViewModel
{
get { return currentViewModel; }
set
{
if (value != currentViewModel)
{
currentViewModel = value;
OnPropertyChanged();
}
}
}
protected virtual void MyCommandExecute(object obj)
{
switch (int.Parse(obj.ToString()))
{
case 1:
CurrentViewModel = new MyViewModel1();
break;
case 2:
CurrentViewModel = new MyViewModel2();
break;
}
}
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = this.PropertyChanged;
if (handler != null)
{
handler.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
public class MyViewModel1 : MyViewModel
{
public RelayCommand<object> MyCommand1 { get; set; }
public MyViewModel1()
{
MyCommand1 = new RelayCommand<object>(MyCommand1Execute);
}
private void MyCommand1Execute(object obj)
{
Debug.WriteLine("MyCommand1");
}
}
public class MyViewModel2 : MyViewModel
{
public RelayCommand<object> MyCommand2 { get; set; }
public MyViewModel2()
{
MyCommand2 = new RelayCommand<object>(MyCommand2Execute);
}
private void MyCommand2Execute(object obj)
{
Debug.WriteLine("MyCommand2");
}
}
The code behind the UserControl1 is
public partial class UserControl1 : UserControl
{
public static readonly DependencyProperty ViewModelProperty = DependencyProperty.Register("ViewModel", typeof(MyViewModel1), typeof(UserControl1));
public UserControl1()
{
InitializeComponent();
}
public MyViewModel1 ViewModel
{
get { return GetValue(ViewModelProperty) as MyViewModel1; }
set { SetValue(ViewModelProperty, value); }
}
}
I have created the ViewModel Property as a DependencyProperty so I can bind to it from the MainWindow.
The Xaml of the user control is
<UserControl x:Class="StackOverflow._20937791.UserControl1" 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:this="clr-namespace:StackOverflow._20937791"
mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300">
<StackPanel DataContext="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type this:UserControl1}}, Path=ViewModel}">
<Button Content="View 1 Command" Command="{Binding Path=MyCommand1}" />
</StackPanel>
</UserControl>
Note I have set up the DataContext on the first content element of the control. The bindings on all child elements are against the ViewModel of the UserControl while any incoming bindings (from the parent control) will be evaluated from the DataContext of that parent control.
Another point to note is that by defining the DataContext in the Xaml, you will get autocomplete in the Binding expressions which will cut down on bad expression errors.
The second UserControl is the same but the ViewModel is of type MyViewModel2.
Finally, the code for the MainWindow is
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
public MyViewModel ViewModel { get; set; }
}
The Xaml is
<Window x:Class="StackOverflow._20937791.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:this="clr-namespace:StackOverflow._20937791"
DataContext="{Binding RelativeSource={RelativeSource Self}, Path=ViewModel}"
Title="MainWindow" Height="200" Width="300">
<Window.Resources>
<DataTemplate DataType="{x:Type this:MyViewModel1}">
<this:UserControl1 ViewModel="{Binding}" />
</DataTemplate>
<DataTemplate DataType="{x:Type this:MyViewModel2}">
<this:UserControl2 ViewModel="{Binding}" />
</DataTemplate>
</Window.Resources>
<StackPanel>
<StackPanel Orientation="Horizontal">
<Button Content="Show View 1" Command="{Binding Path=MyCommand}" CommandParameter="1" Width="100" Margin="4" />
<Button Content="Show View 2" Command="{Binding Path=MyCommand}" CommandParameter="2" Width="100" Margin="0 4" />
</StackPanel>
<ContentControl Content="{Binding Path=CurrentViewModel}" Margin="20" />
</StackPanel>
</Window>
The UserControl is referenced in the main window and it has its ViewModel passed in.
The application shows a window that looks like
I hope this helps.
Firt, FYI - your approach is called the strategy pattern.
Now what you are doing sounds right but it's hard withou seeing your xaml.
Maybe you need to raise a propertychanged event after setting your vm properties?
It would be helpful if you would post your code .But if I havent misunderstood your question then you can try this
<Button Command="{Binding MainControlVM.ClickCommand}"
Set the binding MainControlVM.ClickCommand .Here ClickCommand is the name of your Command.
Update
I think the issue is in Setting the CurrentViewModel. You are setting the CurrentViewModel in the Action Of Command. I think you want to set the CurrentViewModel on the basis of Command. I think this could be better by CommandParameter . Like Bind all Buttons to same Base ViewModel Command and from each Command pass the different CommandParameter and then on Command compare that CommandParameter and set CurrentViewModel accordingly.
ViewModelBase ,Child1ViewModel ,Child2ViewModel
public class ViewModelBase:INotifyPropertyChanged
{
private ICommand _clickCommand;
public ICommand ClickCommand
{
get
{
return _clickCommand ?? (_clickCommand = new CommandHandler(MyAction,()=>true));
}
}
public void MyAction(object obj)
{
if(obj == null )
return;
//if CommandParameter is Cild1VM
if (obj.ToString() == "Child1VM")
CurrentViewModel = new Child1ViewModel();
//if CommandParameter is Cild1VM
else if (obj.ToString() == "Child2VM")
CurrentViewModel = new Child2ViewModel();
}
ViewModelBase _currentViewModel;
public ViewModelBase CurrentViewModel
{
get { return _currentViewModel; }
set
{
if (_currentViewModel == value)
return;
_currentViewModel = value;
this.RaiseNotifyPropertyChanged("CurrentViewModel");
}
}
public event PropertyChangedEventHandler PropertyChanged;
void RaiseNotifyPropertyChanged(string propName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
public class Child1ViewModel : ViewModelBase
{ }
public class Child2ViewModel : ViewModelBase
{ }
xaml
<StackPanel>
<Button Content="Foo" Command="{Binding ClickCommand}" CommandParameter="Child1VM"/>
<Button Content="Bar" Command="{Binding ClickCommand}" CommandParameter="Child2VM"/>
</StackPanel>
xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModelBase();
}
}
I hope this will give you an idea.

Binding on DependencyProperty of custom User Control not updating on change

I'm having difficulties with databinding on my custom user control (s). I created an example project to highlight my problem. I'm completely new to WPF and essentially MVVM as well, so bear with me...
I created a simple view that uses databinding two ways. The databinding on the built-in control works just fine. My custom control doesn't... I put a breakpoint in the PropertyChangedCallback of my control. It gets hit once on startup, but then never again. Meanwhile, the label I have bound to the same value is happily counting down.
What am I missing? My example project follows:
The main window:
<Window x:Class="WpfMVVMApp.MainWindow"
xmlns:local="clr-namespace:WpfMVVMApp"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.DataContext>
<local:CountdownViewModel />
</Grid.DataContext>
<Label Name="custName" Content="{Binding Path=Countdown.ChargeTimeRemaining_Mins}" Height="45" VerticalAlignment="Top"></Label>
<local:UserControl1 MinutesRemaining="{Binding Path=Countdown.ChargeTimeRemaining_Mins}" Height="45"></local:UserControl1>
</Grid>
</Window>
Here's my model:
namespace WpfMVVMApp
{
public class CountdownModel : INotifyPropertyChanged
{
private int chargeTimeRemaining_Mins;
public int ChargeTimeRemaining_Mins
{
get
{
return chargeTimeRemaining_Mins;
}
set
{
chargeTimeRemaining_Mins = value;
OnPropertyChanged("ChargeTimeRemaining_Mins");
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
}
The ViewModel:
namespace WpfMVVMApp
{
public class CountdownViewModel
{
public CountdownModel Countdown { get; set; }
DispatcherTimer timer;
private const int maxMins = 360;
public CountdownViewModel()
{
Countdown = new CountdownModel { ChargeTimeRemaining_Mins = 60 };
// Setup timers
timer = new DispatcherTimer();
timer.Tick += new EventHandler(this.SystemChargeTimerService);
timer.Interval = new TimeSpan(0, 0, 1);
timer.Start();
}
private void SystemChargeTimerService(object sender, EventArgs e)
{
//convert to minutes remaining
// DEMO CODE - TODO: Remove
this.Countdown.ChargeTimeRemaining_Mins -= 1;
}
}
}
Here's the XAML for my user control:
<UserControl x:Class="WpfMVVMApp.UserControl1"
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>
<Label Name="Readout"></Label>
</Grid>
</UserControl>
And here's the code behind the user control:
namespace WpfMVVMApp
{
public partial class UserControl1 : UserControl
{
#region Dependency Properties
public static readonly DependencyProperty MinutesRemainingProperty =
DependencyProperty.Register
(
"MinutesRemaining", typeof(int), typeof(UserControl1),
new UIPropertyMetadata(10, new PropertyChangedCallback(minutesRemainChangedCallBack))
);
#endregion
public int MinutesRemaining
{
get
{
return (int)GetValue(MinutesRemainingProperty);
}
set
{
SetValue(MinutesRemainingProperty, value);
}
}
static void minutesRemainChangedCallBack(DependencyObject property, DependencyPropertyChangedEventArgs args)
{
UserControl1 _readout = (UserControl1)property;
_readout.MinutesRemaining = (int)args.NewValue;
_readout.Readout.Content = _readout.MinutesRemaining;
}
public UserControl1()
{
InitializeComponent();
}
}
}
Your change callback is breaking the binding.
As a skeleton: in your window you have UC.X="{Binding A}" and then in that property change (in UC) you have X=B;. This breaks the binding since in both cases you set X.
To rectify, remove change callback and add this to the label:
Content="{Binding MinutesRemaining, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}"
I tried your code works fine the only change i made was to remove the code behind propertychangedcallback you have and databind the Label (Readout) to the dependency property.
USERCONTROL(XAML)
<UserControl x:Class="WpfApplication1.UserControl"
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>
<Label Name="Readout" Content="{Binding RelativeSource={RelativeSource
AncestorType=UserControl}, Path=MinutesRemaining}"/>
</Grid>
</UserControl>
USERCONTROL (CODE BEHIND)
public partial class UserControl1 : UserControl
{
#region Dependency Properties
public static readonly DependencyProperty MinutesRemainingProperty =
DependencyProperty.Register
(
"MinutesRemaining", typeof(int), typeof(UserControl1),
new UIPropertyMetadata(10)
);
#endregion
public int MinutesRemaining
{
get
{
return (int)GetValue(MinutesRemainingProperty);
}
set
{
SetValue(MinutesRemainingProperty, value);
}
}
public UserControl1()
{
InitializeComponent();
}
}

Categories