Updating user control content from a different control - c#

I am new to wpf format, and ran into a problem at the end of my project.
So let's say I have a top bar, with one textbox and a button.
When the user clicks the button, the user control below this bar should update with the search results from the textbox, and it does, except it does not refresh the UI, only the data storage. For simplicity I will post a demo code modelling the issue, with a single string property.
<!-- the main window -->
<Window.DataContext>
<local:CustomerViewModel/>
</Window.DataContext>
<StackPanel Orientation="Horizontal" VerticalAlignment="Top" Grid.Row="0">
<Label Content="content of the textbox: " Margin="10"/>
<TextBox Width="300" Text="{Binding Customer.Name}"/>
<Button Content="Update" Command="{Binding UpdateCommand}"/>
</StackPanel>
<DockPanel Grid.Row="1">
<local:TestControl />
</DockPanel>
user control:
<!-- the user control named TestControl-->
<UserControl.DataContext>
<local:CustomerViewModel />
</UserControl.DataContext>
<DockPanel LastChildFill="True">
<Label DockPanel.Dock="Top" Content="Saved" />
<Label DockPanel.Dock="Bottom" Content="{Binding Info, UpdateSourceTrigger=PropertyChanged}" />
</DockPanel>
datamodel class:
public class Customer : ObservableObject
{
private string mName;
public string Name
{
get => mName;
set
{
if (value != mName)
{
mName = value;
OnPropertyChanged(nameof(Name));
}
}
}
}
viewmodel class:
public class CustomerViewModel : ObservableObject
{
private Customer mCustomer;
public Customer Customer
{
get => mCustomer;
set
{
if (value != mCustomer)
{
mCustomer = value;
OnPropertyChanged(nameof(Customer));
}
}
}
private string mInfo;
public string Info
{
get => mInfo;
set
{
if (value != mInfo)
{
mInfo = value;
OnPropertyChanged(nameof(Info));
}
}
}
private ICommand mUpdateCommand;
public ICommand UpdateCommand
{
get
{
if (mUpdateCommand == null)
{
mUpdateCommand = new RelayCommand(p => SaveChanges());
}
return mUpdateCommand;
}
}
public void SaveChanges()
{
Info = Customer.Name + " was updated";
MessageBox.Show(Info);
}
public CustomerViewModel()
{
mCustomer = new Customer();
mCustomer.Name = "Test";
Info = mCustomer.Name;
}
}
The correct value is displayed in the messagebox, but it does not change in the user control. I am calling the property changed interface, and have tried to invoke the button press with dispatcher.invoke, same issue, am I missing something very obvious here?

Your usercontrol is creating its own personal instance of the viewmodel, and using that for its DataContext. That usercontrol instance then sets Info on itself, not on the CustomerViewModel that the parent window has for its own datacontext.
<UserControl.DataContext>
<local:CustomerViewModel />
</UserControl.DataContext>
Remove those three lines from your usercontrol. Keep the corresponding lines in the window. The usercontrol will then inherit its datacontext from its parent, and they'll both be on the same page.
Those three lines aren't just declaring the type of viewmodel the view uses; they're creating an actual instance of the class and assigning it to DataContext.

Related

setting the DataContex correctly

im building a UserControl MyUserControl that has his own ViewModel MyUserControlViewModel. MyUserControl contains 6 VehicleSelectionBlock (V1, ... V6). VehicleSelectionBlock is a UserControl i've made. it has 3 RadioButton: car, train, bus; all are of enum type Vehicle and of the same GroupName VehicleGroup.
my goal is to represent each of MyUserControl's VehicleSelectionBlocks in MyUserControlViewModel.
to make my self clear: in MyUserControlViewModel i want to be able to know&change what RadioButton is checked in every one of the 6 VehicleSelectionBlock. i think my main problem is not the converter but rather the DataContex - i'm not sure how to set it correctly for each of the controllers.
iv'e tried Binding (which is the obvious solution). i tried reading here, here , and here. unfortunately neither one helped my acheive my goal.
my code is below - im kinda new to wpf and data binding in generally. i've read almost every chapter in this tutorial but still lost sometimes.
please help me get through this and understand better the DataContex concept.
ty
MyUserContlor.xaml.cs:
namespace Project01
{
/// <summary>
/// Interaction logic for MyUserContlor.xaml
/// </summary>
public partial class MyUserContlor : UserControl
{
public MyUserContlorViewModel ViewModel { get; set; }
public MyUserContlor()
{
ViewModel = new MyUserContlorViewModel();
InitializeComponent();
this.DataContext = ViewModel;
}
private void BtnImReady_OnClick(object sender, RoutedEventArgs e)
{
//this code is irrelevant to the question
throw NotImplementedException();
}
}
}
MyUserContlor.xaml:
<UserControl x:Class="Project01.MyUserContlor"
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:loc="clr-namespace:Project01"
mc:Ignorable="d"
HorizontalContentAlignment="Center" VerticalContentAlignment="Center">
<Viewbox Stretch="Uniform">
<StackPanel>
<loc:VehicleSelectionBlock Name="V1"/>
<loc:VehicleSelectionBlock Name="V2"/>
<loc:VehicleSelectionBlock Name="V3"/>
<loc:VehicleSelectionBlock Name="V4"/>
<loc:VehicleSelectionBlock Name="V5"/>
<loc:VehicleSelectionBlock Name="V6"/>
<Button x:Name="BtnImReady" Click="BtnImReady_OnClick">Im Ready!</Button>
</StackPanel>
</Viewbox>
</UserControl>
MyUserContlorViewModel.cs:
namespace Project01
{
public class MyUserContlorViewModel : INotifyPropertyChanged
{
public MyUserContlorViewModel()
{
VehicleArr = new MyViewModel_Vehicle[6];
PropertyChanged+=MyUserControlViewModel_PropertyChanged;
}
public MyViewModel_Vehicle[] VehicleArr;
public event PropertyChangedEventHandler PropertyChanged;
public PropertyChangedEventHandler GetPropertyChangedEventHandler() { return PropertyChanged; }
private void MyUserControlViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
//might be useful
throw NotImplementedException();
}
}
//this class should represent a VehicleSelectionBlock
public class MyViewModel_Vehicle
{
public Vehicle VehicleSelected {get; set;}
MyViewModel_Vehicle(){}
MyViewModel_Vehicle(Vehicle v){ VehicleSelected = v;}
}
}
VehicleSelectionBlock.xaml:
<UserControl x:Class="Project01.VehicleSelectionBlock"
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:Project01"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid DataContext="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}">
<Border VerticalAlignment="Center" HorizontalAlignment="Center" Background="GhostWhite"
BorderBrush="Gainsboro" BorderThickness="1">
<StackPanel >
<Label Content="{Binding Name}"
FontWeight="Bold" HorizontalContentAlignment="Center"></Label>
<RadioButton GroupName="VehicleGroup" >car</RadioButton>
<RadioButton GroupName="VehicleGroup">train</RadioButton>
<RadioButton GroupName="VehicleGroup" IsChecked="True">bus</RadioButton>
</StackPanel>
</Border>
</Grid>
</UserControl>
VehicleSelectionBlock.xaml.cs:
namespace Project01
{
/// <summary>
/// Interaction logic for VehicleSelectionBlock.xaml
/// </summary>
public partial class VehicleSelectionBlock : UserControl
{
public VehicleSelectionBlock()
{
InitializeComponent();
}
public VehicleSelectionBlock(String name)
{
name = Name;
InitializeComponent();
}
public static readonly DependencyProperty NameProperty = DependencyProperty.Register(
"Name", typeof (String), typeof (VehicleSelectionBlock), new PropertyMetadata(default(String)));
public String Name
{
get { return (String) GetValue(NameProperty); }
set { SetValue(NameProperty, value); }
}
}
public enum Vehicle { Car, Train, Bus}
}
here is a quick solution. keep in mind that the code needs to change if you want to add more values to your Vehicle enum.
the MyUserControlViewModel.cs file
public class MyUserControlViewModel
{
public MyUserControlViewModel()
{
VehicleArr = new VehicleViewModel[6];
for (int i = 0; i < 6;i++ )
VehicleArr[i] = new VehicleViewModel();
}
public VehicleViewModel[] VehicleArr { get; set; }
}
this will expose your 6 items. They could be more. As a result they will be displayed in an ItemsControl, as you will see later.
public class VehicleViewModel:ViewModelBase
{
private bool isCar, isTrain, isBus;
public bool IsCar
{
get { return isCar; }
set
{
if (isCar == value) return;
isCar = value;
OnChanged("IsCar");
}
}
public bool IsTrain
{
get { return isTrain; }
set
{
if (isTrain == value) return;
isTrain = value;
OnChanged("IsTrain");
}
}
public bool IsBus
{
get { return isBus; }
set
{
if (isBus == value) return;
isBus = value;
OnChanged("IsBus");
}
}
}
instances of VehicleViewModel will contain your radio selection using 3 bool properties. this is the solution disadvantage. If you want more values you'll have to add more properties. you can see this inherits ViewModelBase. ViewModelBase just implements INPC so i'm not going to put it here. ViewModelBase also exposes the OnChange method that triggers the INPC event.
displaying the list can be done in your MyUserControl by using an ItemsControl like below.
<ItemsControl ItemsSource="{Binding VehicleArr}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<loc:VehicleControl />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
each item is also a UserControl. The VehicleControl user control is just a StackPanel that displays the RadioButons. This can be seen below.
<StackPanel Orientation="Horizontal">
<RadioButton Content="Car" Margin="5" VerticalAlignment="Center" IsChecked="{Binding Path=IsCar, Mode=TwoWay}"/>
<RadioButton Content="Train" Margin="5" VerticalAlignment="Center" IsChecked="{Binding Path=IsTrain, Mode=TwoWay}"/>
<RadioButton Content="Bus" Margin="5" VerticalAlignment="Center" IsChecked="{Binding Path=IsBus, Mode=TwoWay}"/>
</StackPanel>
please notice that each RadioButton is bound to one of the 3 properties in the VehicleViewModel instance.
Once you press your button you should have all the selections recorded. if you want you could have a function that returns an enum value by analysing the 3 bool properties if that is what you need.
the best solution will be to get rid of the radio buttons and replace them with combo boxes. in this way you can change the enum members and everything will continue to work without changing anything else. this might look as below.
public class VehicleViewModel:ViewModelBase
{
private Vehicle selOption;
private readonly Vehicle[] options;
public VehicleViewModel()
{
this.options = (Vehicle[])Enum.GetValues(typeof(Vehicle));
}
public Vehicle[] Options { get { return options; } }
public Vehicle SelectedOption
{
get { return selOption; }
set
{
if (selOption == value) return;
selOption = value;
OnChanged("SelectedOption");
}
}
}
and for the view:
<ItemsControl ItemsSource="{Binding VehicleArr}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding Options}"
SelectedItem="{Binding SelectedOption, Mode=TwoWay}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
You can do directly in the code-behind of your control (in the default constructor)
public VehicleSelectionBlock()
{
InitializeComponent();
this.DataContext = new MyUserContlorViewModel ();
}
You can also do that in XAML (http://msdn.microsoft.com/en-us/library/ms746695(v=vs.110).aspx) declaration, as you wish.

How to use childwindow in this case

I am working on a silverlight 5 existing application where MVVM approached is followed.
I have created a my own ErrorMessageBox.xaml (childwindow) in View folder and i am in situation where this ErrorMessageBox must be popuped in a class inside Model folder.
And i found that the ErrorMessageBox are not accessible in Model (because it is in View folder).So at last i created one more ErrorMessageBox.xaml inside Model so that it will be used in all
classes in Model folder.
And when i try to popup this child window(ErrorMessageBox.xaml) then it do not pop up. Why it happens and how to Pop up this ErrorMessageBox.xaml inside a function call in a class in Model
folder.
public static void ThisFunctionIsCalledIHaveVerifiedOnDebugging(string message) //it is inside a class in Model folder
{
ConfirmationWindow cfw = new ConfirmationWindow();
cfw.SetMessage("Popup test");
cfw.Show(); //it do not pop up it
}
And ConfirmationWindow.xaml is :
<silvercontrols:ChildWindow x:Class="Model.MessageFolder.ConfirmationWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:silvercontrols="clr-namespace:Silverlight.Windows.Controls;assembly=Silverlight.Windows.Controls"
xmlns:controls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls"
xmlns:toolkit="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Toolkit"
Title="Message" Width="Auto" Height="Auto" MouseRightButtonDown="ChildWindow_MouseRightButtonDown">
<silvercontrols:ChildWindow.Style>
<StaticResource ResourceKey="MessageBoxStyle"/>
</silvercontrols:ChildWindow.Style>
<Grid x:Name="LayoutRoot" MinWidth="360">
<StackPanel Orientation="Vertical">
<TextBlock x:Name="MessageBox" Margin="10 15 0 0" Height="Auto" FontSize="12" Text="Text" Foreground="White" TextWrapping="Wrap" HorizontalAlignment="Left" />
<StackPanel x:Name="ContentBox" Margin="10 15 0 0" Height="Auto" Orientation="Horizontal"></StackPanel>
<StackPanel Margin="0 0 0 10" Orientation="Horizontal" HorizontalAlignment="Center" Height="45">
<Button x:Name="YesBtn" Content="Yes" Width="82" HorizontalAlignment="Left" VerticalAlignment="Bottom" Style="{StaticResource ButtonStyle_Blue}"/>
<Button x:Name="NoBtn" Content="No" Margin="60 0 0 0" Width="82" HorizontalAlignment="Right" VerticalAlignment="Bottom" Style="{StaticResource ButtonStyle_Blue}"/>
</StackPanel>
</StackPanel>
</Grid>
</silvercontrols:ChildWindow>
and ConfirmationWindow.xaml.cs is :
using System.Windows;
namespace Model.MessageFolder
{
public partial class ConfirmationWindow : Silverlight.Windows.Controls.ChildWindow
{
private bool showBtnClose;
public ConfirmationWindow(bool showBtnClose = false)
{
InitializeComponent();
HasCloseButton = showBtnClose;
this.showBtnClose = showBtnClose;
NoBtn.Click += Close;
}
#region METHODS
public void SetMessage(string message)
{
MessageBox.Text = message;
}
public void AddContent(UIElement elt)
{
ContentBox.Children.Add(elt);
}
#endregion
#region EVENT_HANDLER
public void Close(object sender, RoutedEventArgs e)
{
this.Close();
}
#endregion
private void ChildWindow_MouseRightButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
e.Handled = true;
}
}
}
Why it is not working? How to make it work ?
First thing is that you should not bring your childwindow class in the models folder because it breaks the MVVM pattern. Instead leave it in your views folder.
What you should do is to show the childwindow from your model's view.
To achieve that you need a way to tell your view when to show the childwindow and what message it should display.
First, in your model create a property ErrorMessage:
public class MyModel : INotifyPropertyChanged
{
...
private string _errorMessage;
public string ErrorMessage
{
private set
{
_errorMessage = value;
OnPropertyChanged("ErrorMessage");
}
get { return _errorMessage;; }
}
...
}
Note: I assume here that your model class implements INotifyPropertyChanged interface but it could be a different implementation.
Then in your view's code behind add a dependency property and bind it to your model's ErrorMessage.
The dependency property's change callback is used to display the childwindow.
This could look like the following:
public partial class MyView : UserControl
{
...
public static readonly DependencyProperty ErrorMessageProperty =
DependencyProperty.Register("ErrorMessage", typeof (string), typeof (MyView),
new PropertyMetadata((o, args) =>
{
// Display childwindow when message is changed
string message = args.NewValue as string;
if(message!=null)
{
ConfirmationWindow cfw = new ConfirmationWindow();
cfw.SetMessage(message);
cfw.Show();
}
}));
public string ErrorMessage
{
get { return (string)GetValue(ErrorMessageProperty); }
private set { SetValue(ErrorMessageProperty, value); }
}
...
public MyModel ViewModel
{
...
set
{
DataContext = value;
Binding binding = new Binding();
binding.Source = value;
binding.Path = new PropertyPath("ErrorMessage");
SetBinding(ErrorMessageProperty, binding);
}
...
}
...
}
Then every time you change the value of ErrorMessage in your model it should show the childwindow.

WPF Button Command binding

I have a MainWindow:Window class which holds all the data in my program. The MainWindow's .xaml contains only an empty TabControl which is dynamically filled (in the code-behind).
One of the tabs (OptionsTab) has its .DataContext defined as the MainWindow, granting it access to all of the data. The OptionsTab has a DataGrid which has a column populated with Buttons, as shown below:
The DataGrid is populated with DataGridTemplateColumns, where the DataTemplate is defined in the main <Grid.Resources>. I would like to bind this button to a function in the MainWindow (not the OptionsTab in which it resides).
When the OptionsTab is created, it's .DataContext is set as the MainWindow, so I would have expected that defining the DataTemplate as below would have done it.
<DataTemplate x:Key="DeadLoadIDColumn">
<Button Content="{Binding Phases, Path=DeadLoadID}" Click="{Binding OpenDeadLoadSelector}"/>
</DataTemplate>
I thought this would mean the Click event would be bound to the desired OptionsTab.DataContext = MainWindow's function.This, however, didn't work (the Content did, however). So then I started looking things up and saw this answer to another SO question (by Rachel, who's blog has been of great help for me), from which I understood that you can't {bind} the click event to a method, but must instead bind the Command property to an ICommand property (using the helper RelayCommand class) which throws you into the desired method. So I implemented that, but it didn't work. If I place a debug breakpoint at the DeadClick getter or on OpenDeadLoadSelector() and run the program, clicking on the button doesn't trigger anything, meaning the {Binding} didn't work.
I would like to know if this was a misunderstanding on my part or if I simply did something wrong in my implementation of the code, which follows (unrelated code removed):
MainWindow.xaml
<Window x:Class="WPF.MainWindow"
x:Name="Main"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="WPF" SizeToContent="WidthAndHeight">
<TabControl Name="tabControl"
SelectedIndex="1"
ItemsSource="{Binding Tabs, ElementName=Main}">
</TabControl>
</Window>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
ICommand deadClick;
public ICommand DeadClick
{
get
{
if (null == deadClick)
deadClick = new RelayCommand(p => OpenDeadLoadSelector());
return deadClick;
}
}
public ObservableCollection<TabItem> Tabs = new ObservableCollection<TabItem>();
public static DependencyProperty TabsProperty = DependencyProperty.Register("Tabs", typeof(ICollectionView), typeof(MainWindow));
public ICollectionView ITabsCollection
{
get { return (ICollectionView)GetValue(TabsProperty); }
set { SetValue(TabsProperty, value); }
}
public ObservableCollection<NPhase> Phases = new ObservableCollection<NPhase>();
public static DependencyProperty PhasesProperty = DependencyProperty.Register("Phases", typeof(ICollectionView), typeof(MainWindow));
public ICollectionView IPhasesCollection
{
get { return (ICollectionView)GetValue(PhasesProperty); }
set { SetValue(PhasesProperty, value); }
}
public ObservableCollection<string> Loads = new ObservableCollection<string>();
public static DependencyProperty LoadsProperty = DependencyProperty.Register("Loads", typeof(ICollectionView), typeof(MainWindow));
public ICollectionView ILoadsCollection
{
get { return (ICollectionView)GetValue(LoadsProperty); }
set { SetValue(LoadsProperty, value); }
}
void OpenDeadLoadSelector()
{
int a = 1;
}
public MainWindow()
{
var optionsTab = new TabItem();
optionsTab.Content = new NOptionsTab(this);
optionsTab.Header = (new TextBlock().Text = "Options");
Tabs.Add(optionsTab);
ITabsCollection = CollectionViewSource.GetDefaultView(Tabs);
Loads.Add("AS");
Loads.Add("2");
InitializeComponent();
}
}
OptionsTab.xaml
<UserControl x:Class="WPF.NOptionsTab"
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:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
mc:Ignorable="d"
xmlns:l="clr-namespace:WPF">
<Grid>
<Grid.Resources>
<DataTemplate x:Key="DeadLoadIDColumn">
<Button Content="{Binding Phases, Path=DeadLoadID}" Command="{Binding Path=DeadClick}"/>
</DataTemplate>
</Grid.Resources>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<!-- ... -->
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<!-- ... -->
</Grid>
<Grid Grid.Row="1">
<!-- ... -->
</Grid>
<l:NDataGrid Grid.Row="2"
x:Name="PhaseGrid"
AutoGenerateColumns="False">
<l:NDataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="{Binding Path=Name}"/>
<DataGridTextColumn Header="Date (days)" Binding="{Binding Path=Date}"/>
<DataGridTemplateColumn Header="Deadload" CellTemplate="{StaticResource DeadLoadIDColumn}"/>
</l:NDataGrid.Columns>
</l:NDataGrid>
</Grid>
</UserControl>
OptionsTab.xaml.cs
public NOptionsTab(MainWindow w)
{
DataContext = w;
InitializeComponent();
PhaseGrid.ItemsSource = w.Phases;
}
While we're at it (and this might be a related question), why does {Binding Phases, Path=DeadLoadID} work on the DataTemplate (which is why the buttons appear with "Select"), but if I do {Binding Phases, Path=Name} in the PhaseGrid and remove the .ItemsSource code from the constructor, nothing happens? Shouldn't the PhaseGrid inherit its parent's (NOptionsTab / Grid) DataContext? Hell, even setting PhaseGrid.DataContext = w; doesn't do anything without the .ItemsSource code.
EDIT (27/04/14):
I think that knowing the contents of the NPhase class itself will be of use, so here it is:
public class NPhase : INotifyPropertyChanged
{
string name;
double date;
string deadLoadID = "Select";
public event PropertyChangedEventHandler PropertyChanged;
public string Name
{
get { return name; }
set
{
name = value;
EmitPropertyChanged("Name");
}
}
public double Date
{
get { return date; }
set
{
date = value;
EmitPropertyChanged("Date");
}
}
public string DeadLoadID
{
get { return deadLoadID; }
set
{
deadLoadID = value;
EmitPropertyChanged("DeadLoadID");
}
}
void EmitPropertyChanged(string property)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
public NPhase(double _date, string _name)
{
date = _date;
name = _name;
}
}
EDIT (29/04/14):
A simplified project (getting rid of everything that wasn't necessary) can be downloaded from here (https://dl.dropboxusercontent.com/u/3087637/WPF.zip)
I think that there is the problem that you do not specify data source properly for the data item inside your grid.
I think that the data source for your button column is NPhase instance. So it has no DeadClick property. So, you can check it using Output window in Visual Studio.
I suggest that you can do something like that:
<DataTemplate x:Key="DeadLoadIDColumn">
<Button Content="{Binding Phases, Path=DeadLoadID}"
Command="{Binding Path=DataContext.DeadClick, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type l:NDataGrid}}}"/>
</DataTemplate>
I currently do not understand how you can compile Content="{Binding Phases, Path=DeadLoadID}", because as I thought the default value for Binding clause is the Path property, and you have specified it twice.
EDIT
After I got the small solution all becomes clear. Here is the modified solution. All what I changed in it - I have added RelativeSource to the command binding as I described above, and I added MainWindow as DataContext for your OptionsTab (you have specified it in the question, but not in the project). That's it - all works fine - the command getter is called, and the command is executed when you click the button.

Set properties in child UserControl of a current View with ViewModel (MVVM)

I have a main View called MainContentView.xaml in wich I have set the DataContext to a ViewModel called MainContentViewModel.cs. Here I can update the values that appear in the UI, just as a normal MVVM would work.
It looks like this:
<UserControl x:Class="namespace:MyProject.View.Home.MainContentView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:UserTools="clr-namespace:MyProject.View.Tools"
xmlns:ViewModel="clr-namespace:MyProject.ViewModel.Home"
mc:Ignorable="d" >
<UserControl.DataContext>
<ViewModel:MainContentViewModel />
</UserControl.DataContext>
<Grid x:Name="mainGrid">
<!-- Normal objects -->
<StackPanel>
<Label Text="Im a label" />
<TextBox />
</StackPanel>
<!-- My Custom Object -->
<UserTools:MyCustomUserControl x:Name="myUserControl" />
</Grid>
</UserControl>
And my MainContentViewModel.cs looks like this:
public class MainContentViewModel : INotifyPropertyChanged {
public event PropertyChangedEventHandler PropertyChanged;
private string myLabel = "";
public string MyLabel {
get { return this.myLabel; }
set {
this.myLabel = value;
OnPropertyChangedEvent("MyLabel");
}
}
public MainContentViewModel() {
MyLabel = string.Format("my label text");
}
protected virtual void OnPropertyChangedEvent(string _propertyName) {
var _handler = this.PropertyChanged;
if(_handler != null) { _handler(this, new PropertyChangedEventArgs(_propertyName)); }
}
}
Now I want to set some properties in the MyCustomUserControl.xaml through it's own MyCustomUserControlViewModel.cs.
My MyCustomUserControl.xaml looks like this:
<UserControl x:Class="MyProject.View.Tools.MyCustomUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ViewModel="clr-namespace:MyProject.ViewModel.Tools"
mc:Ignorable="d" >
<UserControl.DataContext>
<ViewModel:MyCustomUserControlViewModel />
</UserControl.DataContext>
<Grid x:Name="mainGrid">
<Label Content="{Binding myCustomLabel}" />
</Grid>
</UserControl>
And my MyCustomUserControlViewModel.cs looks like this:
public class MyCustomUserControlViewModel : INotifyPropertyChanged {
public event PropertyChangedEventHandler PropertyChanged;
private string myCustomLabel = "";
public string MyCustomLabel {
get { return this.myCustomLabel; }
set {
this.myCustomLabel = value;
OnPropertyChangedEvent("MyCustomLabel");
}
}
public MyCustomUserControlViewModel() {
MyCustomLabel = string.Format("my custom label text");
}
protected virtual void OnPropertyChangedEvent(string _propertyName) {
var _handler = this.PropertyChanged;
if(_handler != null) { _handler(this, new PropertyChangedEventArgs(_propertyName)); }
}
}
Now I want to, from the MainContentViewModel.cs, update the MyCustomLabel property on the MyUserCustomUserControl.xaml, and I suppose I have to do it through the MyCustomUserControlViewModel.cs so I can keep the MVVM pattern.
I've tried something like this:
In the MainContentViewModel.cs
private MyCustomUserControlViewModel _viewModel = new MyCustomUserControlViewModel ();
(...)
public MainContentViewModel() {
(...)
_viewModel.SetMyCustomLabel("my new custom label text");
}
And in MyCustomUserControlViewModel.cs
public void SetMyCustomLabel(string _text) {
MyCustomLabel = _text;
}
But this does not work. I'm guessing it's because I'm instantiating another MyCustomUserControlViewModel.cs object.
So, how can I do this?
UPDATED AND WORKING (Thank you Sheridan)
I picked up one of Sheridan's solutions and ended up with this working solution. In my MainContentView.xaml:
(...)
<UserTools:MyCustomUserControl DataContext="{Binding ChildViewModel, Mode=OneWay}" />
In my MainContentViewModel.cs:
private MyCustomUserControlViewModel childViewModel = new MyCustomUserControlViewModel();
public MyCustomUserControlViewModel ChildViewModel {
get { return childViewModel; }
private set { childViewModel = value; OnPropertyChangedEvent("ChildViewModel"); }
}
And then I can do this:
public MainContentViewModel() {
(...)
ChildViewModel.SetMyCustomLabel("my new custom label text");
}
If your parent view model had access to the actual child view model instance that is used (which it probably doesn't because of the way that you instantiate your child view model), then you could have simply done this:
public MainContentViewModel() {
(...)
_viewModel.CustomLabel = "my new custom label text";
}
1.
One solution would be to remove the child view model from the child view to the parent view model and then to display the child view using a ContentControl and a DataTemplate:
<DataTemplate DataType="{x:Type ViewModels:MyCustomUserControlViewModel}">
<Views:MyCustomUserControlView />
</DataTemplate>
...
In MainContentViewModel:
public MyCustomUserControlViewModel ChildViewModel
{
get { return childViewModel; }
private set { childViewModel = value; NotifyPropertyChanged("ChildViewModel"); }
}
...
<Grid x:Name="mainGrid">
<!-- Normal objects -->
<StackPanel>
<Label Text="Im a label" />
<TextBox />
</StackPanel>
<!-- My Custom Object -->
<ContentControl x:Name="myUserControl" Content="{Binding ChildViewModel}" />
</Grid>
Doing this would enable you to call the property simply, as shown in my first example.
2.
One alternative would be to move the property to the parent view model and bind to it directly from the child UserControl:
<Grid x:Name="mainGrid">
<Label Content="{Binding DataContext.myCustomLabel, RelativeSource={RelativeSource
AncestorType={x:Type Views:MainContentView}}}" />
</Grid>
UPDATE >>>
3.
Ok, I've just had another idea... you could add another property into the parent view model and data bind to the child view model like this maybe:
<UserTools:MyCustomUserControl DataContext="{Binding ChildViewModel,
Mode=OneWayToSource}" x:Name="myUserControl" />
This is just a guess, but it might be able to hook onto the child view model like this... then you could do this in the parent view model:
if (ChildViewModel != null) ChildViewModel.CustomLabel = "my new custom label text";
4.
Actually... I've just had another idea. You could do bind from a property in your parent view model through to the UserControl if you add a DependencyProperty to it:
<UserTools:MyCustomUserControl CustomLabel="{Binding CustomLabelInParentViewModel}"
x:Name="myUserControl" />
You could simply remove the property from the child view model in this case.

In mvvm the view doesn't update

i am beginner in mvvm concept.so i tried one sample application.it contains two textbox are name and id, one submit button,one label.when i click submit button it combine the two strings from the textbox and display in label.
i can submit the values and in viewmodel the property contain the result.but its not shown in view.why..?
in view contains
<grid>
<TextBox Grid.Column="1" Grid.Row="1" Text="{Binding name}" Height="23" Margin="9" Name="txtname" Width="120" />
<TextBox Grid.Column="1" Grid.Row="2" Text="{Binding id}" Height="23" Margin="9" Name="txtid" Width="120" />
<Button Command="{Binding submit}" Content="submit" Grid.Column="2" Grid.Row="2" Height="23" Name="btnsubmit" Width="75" />
<Label Content="{Binding display}" Grid.Row="3" Height="38" HorizontalAlignment="Left" Name="lbldisplay" Width="192" />
</grid>
view.cs code is
public MainWindow()
{
InitializeComponent();
DataContext = new DemoViewModel();
}
in my viewmodel contains two .cs file
one is DemoViewModelInotify.cs.in this i write the code for inotifypropertychanged.
another one is DemoViewModel.cs.this contain the property and commands.
namespace mvvmdemonixon.ViewModel
{
public class DemoViewModel :DemoViewModelInotify
{
public ICommand submit { get; set; }
public string name { get; set; }
public string display { get; set; }
public int id{get;set;}
public DemoModel model { get; set; }
public DemoViewModel()
{
submit = new DelegateCommand<object>(this.add);
}
public void add(object paramter)
{
string a= mvvmdemonixon.Model.DemoModel.addstring(name, id);
display = a;
}
}
}
my model contains
namespace mvvmdemonixon.Model
{
public class DemoModel
{
public static string addstring(string name1, int no1)
{
string display = "The Student Name Is " + name1 + "and Id Is" + no1 + ".";
return display;
}
}
}
in my app.xaml.cs
private void OnStartup(object sender, StartupEventArgs e)
{
mvvmdemonixon.MainWindow view = new MainWindow();
view.DataContext = new mvvmdemonixon.ViewModel.DemoViewModel();
view.Show();
}
in my app xaml
<Application x:Class="mvvmdemonixon.App"
Startup="OnStartup">
</Application>
advance thanks..
WPF binding engine uses INotifyPropertyChanged (for scalar properties) and INotifyCollectionChanged (for collection properties) interfaces to reflect changes, which has been made in bound data source (view model).
This:
public string display { get; set; }
means, that any property setting will not notify the view, because setter's code doesn't contain any notifications. The code should be like this:
public string display
{
get { return _display; }
set
{
if (_display != value)
{
_display = value;
OnPropertyChanged("display");
}
}
}
private string _display;
where OnPropertyChanged raises INotifyPropertyChanged.PropertyChanged event.
This is true for other view-bound properties in your view model, which should update the view.
You need to implement INotifyPropertyChanged in your ViewModel.
And raise the property changed event from each property setter.
There are two things you were missing. The first thing is your View's binding is OneTime. The second thing is you ViewModel does not notify when a property changed.
To fix the first problem, you have to update xaml like below.
<Label Content="{Binding display, Mode=OneWay}" Grid.Row="3" Height="38" HorizontalAlignment="Left" Name="lbldisplay" Width="192" />
To fix your second problem, you have to implement INotifyPropertyChanged.

Categories

Resources