Dependency property not binding to view - c#

what im tryin to do is populate a combobox based upon the selected item of a treeview. as depending on what node is chosen will populate the combobox with a different list of report levels.
the combobox is within a user control and i am trying to bind to a dependency property ReportLevel i have in my MainViewModel. if i set the value of the combobox its fine but i want to be able to update it whenever a user choses a different node on the tree.
here is my xaml
<UserControl x:Name="this"
x:Class="RTHM.ComboboxControl1"
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"
xmlns:local ="clr-namespace:RTHM.ViewModels"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<StackPanel HorizontalAlignment="Center" MinHeight="221.904" Margin="12,12,42,0" VerticalAlignment="Top" MaxWidth="246.226" Width="246">
<WrapPanel HorizontalAlignment="Left" MinHeight="224.072" VerticalAlignment="Top" MinWidth="246.13">
<TextBox HorizontalAlignment="Left" MinHeight="104.489" Margin="10,289.95,0,0" TextWrapping="Wrap" Text="{Binding ReportDescription, Mode=TwoWay}" VerticalAlignment="Top" MinWidth="245.124"/>
<TextBox MinHeight="23" TextWrapping="Wrap" Text="Report Level:" MinWidth="120" BorderThickness="1" Margin="0,0,5,5"/>
<ComboBox MinWidth="120" Margin="0,0,0,5" MinHeight="23"
ItemsSource="{Binding ElementName=this, Path=ReportLevel,Mode=TwoWay}"
IsSynchronizedWithCurrentItem="True"
DisplayMemberPath="Name"
SelectedItem="{Binding Path=SelectedLevel, Mode=TwoWay}"
IsEnabled="{Binding IsEnabled}"/>
my code behind
public partial class ComboboxControl1 : UserControl
{
public ComboboxControl1()
{
InitializeComponent();
DataContext = new MainViewModel();
in my MainViewModel i have this
#region DependencyProperties
public static readonly DependencyProperty LevelProperty =
DependencyProperty.Register("ReportLevel",typeof(ObservableCollection<ReportLevel>),typeof(MainViewModel));
public ObservableCollection<ReportLevel> ReportLevel
{
get
{
return (ObservableCollection<ReportLevel>)GetValue(LevelProperty);
}
set
{
SetValue(LevelProperty, value);
}
}
and have a method which sets the value of this like
ReportLevel = c.GetUserReportLevel();
i have tried various things with the ItemSource of the combobox but without success.
Any help on the matter would be appreciated
Thanks,
Marty
EDIT: Have updated my code but still no luck with this matter, any ideas?
Xaml
<UserControl x:Class="RTHM.ComboboxControl1"
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"
xmlns:local ="clr-namespace:RTHM.ViewModels"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<StackPanel HorizontalAlignment="Center" MinHeight="221.904" Margin="12,12,42,0" VerticalAlignment="Top" MaxWidth="246.226" Width="246">
<WrapPanel HorizontalAlignment="Left" MinHeight="224.072" VerticalAlignment="Top" MinWidth="246.13">
<TextBox HorizontalAlignment="Left" MinHeight="104.489" Margin="10,289.95,0,0" TextWrapping="Wrap" Text="{Binding ReportDescription, Mode=TwoWay}" VerticalAlignment="Top" MinWidth="245.124"/>
<TextBox MinHeight="23" TextWrapping="Wrap" Text="Report Level:" MinWidth="120" BorderThickness="1" Margin="0,0,5,5"/>
<ComboBox MinWidth="120" Margin="0,0,0,5" MinHeight="23"
ItemsSource="{Binding Path=Levels}"
IsSynchronizedWithCurrentItem="True"
DisplayMemberPath="Name"
SelectedItem="{Binding Path=SelectedLevel, Mode=TwoWay}"
IsEnabled="{Binding IsEnabled}"/>
and in MainViewModel
private ObservableCollection<ReportLevel> _levels;
public ObservableCollection<ReportLevel> Levels
{
get
{
return _levels;
}
set
{
_levels = value;
NotifyPropertyChanged("Levels");
}
}
my MainViewModel inherits from a base class which has INotifyProperty changed and an implementation
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}

The binding
ItemsSource="{Binding ElementName=this, Path=ReportLevel, Mode=TwoWay}"
binds to a ReportLevel property in your UserControl. There is however no such property, because it is in the DataContext of the UserControl. The binding should look like shown below. Please also note that a two-way binding on the ItemsSource property doesn't make sense, as the control never sets that property.
ItemsSource="{Binding Path=ReportLevel}"
That said, you do usually not have dependency properties in view models. Instad, the view model classes should implement the INotifyPropertyChanged interface.
You also have to actually fire the PropertyChanged event when the ReportLevel property changes:
private ObservableCollection<ReportLevel> reportLevel;
public ObservableCollection<ReportLevel> ReportLevel
{
get { return reportLevel; }
set
{
reportLevel = value;
OnPropertyChanged("ReportLevel");
}
}

Related

Strange UpdateSourceTrigger behaviour for combobox

I have a combo box for which I change the values of the selected items in the bound property setter. The value is not reflected in the combobox in View. When I select 2 in UI, it should show 3 but it shows 2.
I don't understand why it doesn't work even though I set the updatesourcetrigger to Propertychanged.
public class MyViewModel : INotifyPropertyChanged
{
private int _selected;
public List<int> MyList => new List<int>() {1, 2, 3};
public int Selected
{
get => _selected;
set
{
_selected = value;
if (value == 2)
_selected = 3;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
and my view is
<Window x:Class="WpfApp2.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:WpfApp2"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<StackPanel VerticalAlignment="Center">
<ComboBox Height="50" Width="200" ItemsSource="{Binding MyList}" SelectedItem="{Binding Selected,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" ></ComboBox>
</StackPanel>
However, I noticed that if I add a Delay or change UpdateSourceTrigger to LostFocus, it works fine.
Can anyone explain why it doesn't work in the first case even if propertychanged is fired from source to target?
Code with a delay which works
<ComboBox Height="50" Width="200" ItemsSource="{Binding MyList}" SelectedItem="{Binding Selected,Mode=TwoWay,Delay=1}" ></ComboBox>
Also out of curiosity I added a textbox to my view ,binded the same with selecteditem with same updatesourcetrigger. But to my surprise when I enter 2 in textbox,it changes to 3 and updates combobox too.I find it difficult to understand why this doesnt work witn combobox?
<StackPanel VerticalAlignment="Center">
<ComboBox Height="50" Width="200" ItemsSource="{Binding MyList}" SelectedItem="{Binding Selected,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" ></ComboBox>
<TextBox Height="50" Width="200" Text="{Binding Selected,Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" ></TextBox>
</StackPanel>

WPF Binding to a DependencyProperty problem with an custom User Control inside a Window

I need some help. I created a custom User Control, and inserted it into the Main Window. However, Im not able to bind a Property in the Window to a DependencyProperty in the User Control.
Here's the User Control code.
XAML:
<UserControl x:Class="SomeExample.UCFullName"
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:SomeExample"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<StackPanel Margin="0,0,0,0" Orientation="Vertical" >
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<StackPanel Orientation="Vertical">
<Label BorderBrush="White" BorderThickness="1" Content="First Name :" FontSize="14" FontWeight="SemiBold" Foreground="White" Height="30" HorizontalAlignment="Left" Margin="0,2,0,0" VerticalAlignment="Top" Width="100"/>
</StackPanel>
<Grid>
<TextBox Name="FirstName" BorderBrush="Black" BorderThickness="1" FontSize="14" FontWeight="SemiBold" Height="30" HorizontalAlignment="Left" Margin="2,2,0,0" MaxLength="20" VerticalAlignment="Top" Width="100" TextChanged="TxtBlock_TextChanged"/>
</Grid>
</StackPanel>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<StackPanel Orientation="Vertical">
<Label BorderBrush="White" BorderThickness="1" Content="Last Name :" FontSize="14" FontWeight="SemiBold" Foreground="White" Height="30" HorizontalAlignment="Left" Margin="0,2,0,0" VerticalAlignment="Top" Width="100"/>
</StackPanel>
<Grid>
<TextBox Name="LastName" BorderBrush="Black" BorderThickness="1" FontSize="14" FontWeight="SemiBold" Height="30" HorizontalAlignment="Left" Margin="2,2,0,0" MaxLength="20" VerticalAlignment="Top" Width="100" TextChanged="TxtBlock_TextChanged"/>
</Grid>
</StackPanel>
</StackPanel>
And here's the code behind
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
namespace SomeExample
{
public partial class UCFullName : UserControl, INotifyPropertyChanged
{
#region INotifyPropertyChanged implementation
public event PropertyChangedEventHandler PropertyChanged;
protected void Notify(string propertyName)
{
if (this.PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion INotifyPropertyChanged implementation
public string ValueFullName
{
get { return (string)GetValue(ValueFullNameProperty); }
set
{
SetValue(ValueFullNameProperty, value);
Notify("ValueFullName");
}
}
// Using a DependencyProperty as the backing store for Value. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ValueFullNameProperty =
DependencyProperty.Register("ValueFullName", typeof(string), typeof(UCFullName), new FrameworkPropertyMetadata(default(string), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public UCFullName()
{
InitializeComponent();
}
private void TxtBlock_TextChanged(object sender, TextChangedEventArgs e)
{
ValueFullName = FirstName.Text + " " + LastName.Text;
}
}
}
This is how it looks:
And here's the code of the Main Window:
<Window x:Class="SomeExample.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:SomeExample"
mc:Ignorable="d"
Title="Some Example" Height="200" Width="400">
<StackPanel Margin="0,0,0,0" Orientation="Vertical" Name="SpManual" Background="Black">
<GroupBox Header="Name" Foreground="White" FontSize="14" Name="groupBoxCoordinateStart" >
<local:UCFullName ValueFullName="{Binding Path = PropertyFullName, Mode = TwoWay, RelativeSource={RelativeSource AncestorType=UserControl}}"></local:UCFullName>
</GroupBox>
<StackPanel Name="SpBtnInsert" Orientation="Horizontal" HorizontalAlignment="Center" Visibility="Visible">
<Button Name="btnShowFullName" BorderBrush="White" BorderThickness="1" FontSize="14" FontWeight="SemiBold" Height="30" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="2,2,0,0" Background="Transparent" Content="Show Name" Foreground="White" Width="98" Click="BtnShowFullName_Click"></Button>
</StackPanel>
</StackPanel>
And the code behind:
using System.Windows;
namespace SomeExample
{
/// <summary>
/// Lógica de interacción para MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public string PropertyFullName { get; set; }
public MainWindow()
{
InitializeComponent();
}
private void BtnShowFullName_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show("Current full name :" + PropertyFullName);
}
}
}
And of course, I expected that when I pressed the button, I got a message with the full name entered by the user. However, I got nothing.
Edit: Here's the solution to the problem, for people who visit this page with a similar problem.
<local:UCFullName ValueFullName="{Binding Path = PropertyFullName, RelativeSource={RelativeSource AncestorType=Window}}"></local:UCFullName>
You are binding to the wrong AncestorType. Instead of UserControl the type must be Window. Window extends Control but not UserControl.
<local:UCFullName ValueFullName="{Binding Path=PropertyFullName, Mode=TwoWay, RelativeSource={RelativeSource AncestorType=Window}}" />
Also because you set the Binding.Mode to TwoWay, the binding source PropertyFullName must be able to notify the binding target ValueFullName about value changes. To achieve this, you need to implement PropertyFullName as a DependencyProperty to enable two way binding.
As a a side note:
The following code can be problematic
public string ValueFullName
{
get { return (string)GetValue(ValueFullNameProperty); }
set
{
SetValue(ValueFullNameProperty, value);
Notify("ValueFullName"); // This line might never get called
}
}
This is just a CLR wrapper for the actual DependencyProperty and will never be invoked by the framework. When using the binding on this property the wrapper will never get called and therefore the event will never get raised.
As BionicCode has pointed out, you can change the AncestorType. Another option is to set DataContext of the Window.
You can either do it in the constructor
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
or in the XAML.
<Window DataContext="{Binding Mode=OneWay, RelativeSource={RelativeSource Self}}">
This way you don't have to specify source in your bindings (as long as you bind to code-behind properties).

WPF update textblock from different window

I have a window called RolledPaper.xaml with a textblock called SequenceValue.
SequenceValue is defined in another window called CounterSettings.xaml by typing in a textbox called SequenceRequested. I would like to have SequenceValue be always in sinch with SequenceRequested.
I tryed and failed using the same datacontext for both windows.
here it is the code for RolledPaper.xaml:
<Window x:Class="Numbering3.View.RolledPaper"
WindowStyle="None"
ResizeMode="NoResize"
BorderBrush="LightGray"
BorderThickness="5"
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:Numbering3.View"
xmlns:vm="clr-namespace:Numbering3.ViewModel"
xmlns:cv="clr-namespace:Numbering3.Helper"
xmlns:vo="clr-namespace:Numbering3.ViewModel"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
mc:Ignorable="d"
Title="RolledPaper" Height="750" Width="1218"
Background="{DynamicResource WindowBrush}"
DataContextChanged="Rolledpaper_DataContextChanged">
<Window.DataContext>
<vm:ViewModelRolledPaper/>
</Window.DataContext>
<TextBlock x:Name="SequenceValue" Grid.Column="3" HorizontalAlignment="Left" Height="19" Margin="72,310,0,0" TextWrapping="Wrap"
Background="White" VerticalAlignment="Top" Width="98" Text="{Binding _SequenceValueToShow.SequenceValuetoShow, Mode=TwoWay, FallbackValue=NNN, TargetNullValue=NNN, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
code behind:
public partial class RolledPaper : Window
{
public RolledPaper()
{
WindowStartupLocation = System.Windows.WindowStartupLocation.CenterScreen;
SaveKeeper.fromMain = false;
InitializeComponent();
this.MouseLeftButtonDown += delegate { this.DragMove(); };
DataContextChanged += new DependencyPropertyChangedEventHandler(Rolledpaper_DataContextChanged);
}
the CounterSetting window:
<Window x:Class="Numbering3.View.CounterSettings"
...
...
<Window.DataContext>
<ViewModelCounterSettings/>
</Window.DataContext>
<Grid Margin="0,-19,-0.4,0">
<TextBox x:Name="SequenceRequested" PreviewTextInput="SequenceValidationTextBox" MaxLength="10" HorizontalAlignment="Left" Height="23" Margin="154,324,0,0" TextWrapping="Wrap"
Text="{Binding Path=_SequenceValueToShow.SequenceValueToKeep, FallbackValue='NNN', TargetNullValue ='NNN', Mode=TwoWay, UpdateSourceTrigger=PropertyChanged }" VerticalAlignment="Top" Width="120" TextChanged="SequenceRequested_TextChanged" />
</Grid>
its code behind:
public partial class CounterSettings : Window
{
public CounterSettings()
{
InitializeComponent();
this.MouseLeftButtonDown += delegate { this.DragMove(); };
DataContextChanged += new DependencyPropertyChangedEventHandler(CounterSettings_DataContextChanged);
}
And the SequeneValue class:
public class SequenceValue : INotifyPropertyChanged
{
public string SequenceValueToKeep
{
get
{
return _sequenceValueToKeep=_sequenceProcessor.GetSequence();
}
set
{
if (_sequenceValueToKeep != value)
{
__sequenceValueToKeep = value;
RaisePropertyChanged("SequenceValueToKeep");
}
}
}
private void RaisePropertyChanged(string prop)
{
if (PropertyChanged != null)
{ PropertyChanged(this, new PropertyChangedEventArgs(prop)); }
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
I need to put a value in the textbox of countersetting =>string Sequencevalue.SequenceValueToKeep is updated => textblock in RolledPaper window shows Sequencevalue.SequenceValueToKeep .
Thank you.
Solutio 1:
You can use one Static ViewModel to get the to to know each other.
in App.xaml
<Application.Resources>
...
<MainViewModel x:Key="mainViewModel" />
</Application.Resources>
and then in CounterSetting.xaml
<Window otherProperties=...
DataContext="{Binding ViewModelCounterSettings, Source={StaticResource mainViewModel}}"/>
and in RolledPaper.xaml
<Window otherProperties=...
DataContext="{Binding ViewModelRolledPaper, Source={StaticResource mainViewModel}}"/>
The MainViewModel does inits your two ViewModel and makes them accessible as properties and also gives the the same instance of SequenceValue
Solution 2:
Create an object of SequenceValue as an static resource, like
<Application.Resources>
...
<SequenceValue x:Key="sequenceValue"/>
</Application.Resources>
and set it as the DataContext of your TextBox and TextBlock
<TextBox DataContext="{StaticResource sequenceValue}"
Text="{Binding SequenceValueToKeep, FallbackValue='NNN', TargetNullValue='NNN', Mode=TwoWay, UpdateSourceTrigger=PropertyChanged }"
... />
<TextBlock DataContext="{StaticResource sequenceValue}"
Text="{Binding SequenceValuetoShow, FallbackValue=NNN, TargetNullValue=NNN, UpdateSourceTrigger=PropertyChanged}"

RaisePropertyChanged Triggering "set" On Different Property

I was working on a project when I came across an issue with RaisePropertyChanged from MVVM Light that I can't seem to figure out. When I try to raise a change for my list, the list does get updated, but as does the selected index value above. The value that is passed to my selected index appears to be influenced by what key was pressed to trigger the event (i.e. if I press "BACKSPACE", the value passed to the setter is "-1", whereas if I enter a letter, the value passed is "0")
I recreated a project which purely demonstrates the issue. Below is the main bit of logic found in the MainVeiwModel:
public class MainViewModel : ViewModelBase
{
public MainViewModel()
{
_testItems = new List<TestItem>()
{
new TestItem() { Name = "Test1" },
new TestItem() { Name = "Test2" }
};
}
public int SelectedIndex
{
get { return _selectedIndex; }
set
{
_selectedIndex = value;
RaisePropertyChanged("SelectedIndex");
RaisePropertyChanged("SelectedText");
RaisePropertyChanged("TestList");
}
}
public string SelectedText
{
get
{
return _testItems[_selectedIndex].Name;
}
set
{
_testItems[_selectedIndex].Name = value;
RaisePropertyChanged("TextList");
}
}
public List<string> TextList
{
get
{
_textList = new List<string>();
if (_testItems != null && _testItems.Count > 0)
{
foreach (TestItem item in _testItems)
_textList.Add(item.Name);
}
return _textList;
}
set { _textList = value; }
}
private int _selectedIndex;
private List<string> _textList;
private List<TestItem> _testItems;
}
My XAML:
<Window x:Class="RaisePropertyBug.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:RaisePropertyBug"
mc:Ignorable="d"
DataContext="{Binding Source={StaticResource Locator}, Path=Main}"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<ComboBox ItemsSource="{Binding TextList, UpdateSourceTrigger=PropertyChanged}"
SelectedIndex="{Binding SelectedIndex, UpdateSourceTrigger=PropertyChanged}"
HorizontalAlignment="Center" VerticalAlignment="Center"/>
<TextBox Grid.Row="1" Text="{Binding SelectedText, UpdateSourceTrigger=PropertyChanged}"
HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Grid>
For context: I have a ComboBox which lists the names from a collection of items I have. There is an edit window where users can change names and other properties of these items. My goal is to have the ComboBox list update as the user edits the value. In my actual program, you are able to do this with the item at index 0, but any other index will automatically change to 0 as soon as a key is pressed and the RaisePropertyChanged() area is reached.
Check below code if it's working as per your requirement.
Use SelectedItem property of ComboBox and bind selecteditem to the edit screen/textbox. I've bind SelectedTestItem.Name propety here.
View -
<Window x:Class="StackOverflow.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:StackOverflow"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<ComboBox ItemsSource="{Binding TestItems, UpdateSourceTrigger=PropertyChanged}" DisplayMemberPath="Name" SelectedValuePath="Name"
SelectedItem="{Binding SelectedTestItem, UpdateSourceTrigger=PropertyChanged}"
HorizontalAlignment="Center" VerticalAlignment="Center"/>
<TextBox Grid.Row="1" Text="{Binding SelectedTestItem.Name, UpdateSourceTrigger=PropertyChanged}" Width="200"
HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Grid>
View.cs -
public partial class MainWindow : Window, INotifyPropertyChanged
{
private TestItem selectedTestItem;
public TestItem SelectedTestItem
{
get { return selectedTestItem; }
set
{
selectedTestItem = value;
RaisePropertyChanged("SelectedTestItem");
}
}
public List<TestItem> TestItems
{
get;
set;
}
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
var items = new List<TestItem>()
{
new TestItem() { Name = "Test1" },
new TestItem() { Name = "Test2" }
};
TestItems = items;
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string prop)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(prop));
}
}
}
You don't even need INotifyPropertyChanged for this example. I'm not entirely certain of what you are trying to achieve, but this code will achieve what I have gleaned from your post.
<Window x:Class="RaisePropertyChangedExample.BindingExample"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Correct View" Width="150" Height="80">
<StackPanel Orientation="Vertical">
<ComboBox ItemsSource="{Binding Items}"
x:Name="ItemViews"
HorizontalAlignment="Stretch" VerticalAlignment="Center" DisplayMemberPath="Name"/>
<TextBox DataContext="{Binding SelectedItem, ElementName=ItemViews}" Text="{Binding Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
HorizontalAlignment="Stretch" VerticalAlignment="Center"/>
</StackPanel>
</Window>
and the supporting code
using System.Windows;
using System.Collections.Generic;
using System.Collections.ObjectModel;
namespace RaisePropertyChangedExample
{
public partial class BindingExample : Window
{
public BindingExample()
{
InitializeComponent();
DataContext = new BindingExampleViewModel();
}
}
public class BindingExampleViewModel
{
public ObservableCollection<TestItemViewModel> Items { get; set; }
= new ObservableCollection<TestItemViewModel>(new List<TestItemViewModel>
{
new TestItemViewModel {Name = "Test1"},
new TestItemViewModel {Name = "Test2"}
});
}
public class TestItemViewModel
{
public string Name { get; set; }
}
}
Unless there is some need of the index of the selected Item, there is no real argument against simply exposing each item as a TestItemViewModel view model and binding the other controls directly to the selected item itself. If however other controls are bound to the members of the TestItemViewModel then it's still not necessarily true that you should implement INotifyPropertyChanged on that view model.
The following example will still display the correct information when wired up with the existing ViewModel:
<Window x:Class="RaisePropertyChangedExample.BindingExample"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Correct View" Width="150" Height="100">
<StackPanel Orientation="Vertical">
<ComboBox ItemsSource="{Binding Items}"
x:Name="Combo"
HorizontalAlignment="Stretch" VerticalAlignment="Center" DisplayMemberPath="Name"/>
<Grid DataContext="{Binding SelectedItem, ElementName=Combo}">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBox Text="{Binding Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
HorizontalAlignment="Stretch" VerticalAlignment="Center"/>
<Label Grid.Row="1" HorizontalAlignment="Stretch" Height="20" Content="{Binding Name}" />
</Grid>
</StackPanel>
</Window>
Normally
Updating after every keystroke can diminish performance and it denies the user the usual opportunity to backspace and fix typing errors before committing to the new value. see MS reference
however, this is only an issue if other processing occurs as the result of updating the source. If you are worried about the amount of processing involved you can switch to the default behaviour of LostFocus by simply omitting `UpdateSourceTrigger' declaration.

WinRT MVVM-Light ComboBox in ListView with different ItemsSources

I'm working on a WinRT application where i have a Listview with a ComboBox.
The Listview has a particular ObservableCollection as Itemssource, The ComboBox Should have another ObservableCollection as ItemsSource because i should be able to dynamicaly change the contents of the ComboBox.
I'm using the MVVM-Light framework, The ObservableCollections are filled in the ViewModel and displayed through databinding.
I'll give you an example Xaml code:
<Page x:Class="MvvmLight2.MainPage"
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:ignore="http://www.ignore.com"
mc:Ignorable="d ignore"
d:DesignHeight="768"
d:DesignWidth="1366"
DataContext="{Binding Main, Source={StaticResource Locator}}">
<Page.Resources>
</Page.Resources>
<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
<ListView ItemsSource="{Binding CollectionOne}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding StringOne}"></TextBlock>
<ComboBox ItemsSource="{Binding CollectionTwo}" Width="500">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding StringTwo}"></TextBlock>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
And Corresponding ViewModel:
public class MainViewModel : ViewModelBase
{
private readonly IDataService _dataService;
public MainViewModel(IDataService dataService)
{
_dataService = dataService;
CollectionOne = new ObservableCollection<ClassOne>();
for (int i = 0; i < 4; i++)
{
var temp = new ClassOne()
{
StringOne = "String " + i.ToString()
};
CollectionOne.Add(temp);
}
CollectionTwo = new ObservableCollection<ClassTwo>();
CollectionTwo.Add(new ClassTwo("ADV"));
CollectionTwo.Add(new ClassTwo("Wettelijk"));
}
private ObservableCollection<ClassOne> _collectionOne;
public ObservableCollection<ClassOne> CollectionOne
{
get { return _collectionOne; }
set
{
if (_collectionOne == value)
{
return;
}
_collectionOne = value;
RaisePropertyChanged(() => CollectionOne);
}
}
private ObservableCollection<ClassTwo> _collectionTwo;
public ObservableCollection<ClassTwo> CollectionTwo
{
get { return _collectionTwo; }
set
{
if (_collectionTwo == value)
{
return;
}
_collectionTwo = value;
RaisePropertyChanged(() => CollectionTwo);
}
}
}
In ClassOne and ClassTwo are for the example just one property in each class with a string.
Both collections have to remain seperate because they can be different in length when randomly filled.
EDIT
#Josh I followed your instructions but it still doesn't seem to work, Here are my adjustments:
<Page x:Class="MvvmLight2.MainPage"
x:Name="MyControl"
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:ignore="http://www.ignore.com"
mc:Ignorable="d ignore"
d:DesignHeight="768"
d:DesignWidth="1366"
DataContext="{Binding Main, Source={StaticResource Locator}}">
<Page.Resources>
</Page.Resources>
<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
<ListView ItemsSource="{Binding CollectionOne}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding StringOne}"></TextBlock>
<ComboBox ItemsSource="{Binding ElementName=MyControl, Path=CollectionTwo}" Width="500">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding StringTwo}"></TextBlock>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
Since you are using the ViewModel Locator to set your datacontext you can reuse this to find the property CollectionTwo.
Your binding would look like this:
<ComboBox ItemsSource="{Binding Path=Main.CollectionTwo, Source={StaticResource Locator}}" />
You need to move up one level in the datacontext to search the view model instead of the item that is bound at the list view level using RelativeSource:
<ComboBox ItemsSource="{Binding RelativeSource={RelativeSource AncestorType={x:Type Page}}, Path=CollectionTwo}" />
and for WinRT situations, use a control name:
ElementName=MyControl
instead of searching by AncestorType and give the page a name of 'MyControl'. It would then look like
<ComboBox ItemsSource="{Binding ElementName=MyControl, Path=DataContext.CollectionTwo}" />
and your Page would look like
<Page x:Name="MyControl"
The ComboBox binding is relative to the Binding of the ListItem. So it searches for CollectionTwo as a property of ClassOne. Either look at the RelativeSource to bind to or move the CollectionTwo to class ClassOne. That way, you can easily build up different lists for each ListViewItem.

Categories

Resources