I have a table with checkboxes that is bound to the ObservableCollection > collection, I want to track changes to this collection when one of the checkboxes changes my view.
This is my code:
<UserControl.Resources>
<DataTemplate x:Key="DataTemplate_Level2">
<CheckBox IsChecked="{Binding Path=. ,Mode=TwoWay}" Height="40" Width="50" Margin="4,4,4,4"/>
</DataTemplate>
<DataTemplate x:Key="DataTemplate_Level1">
<ItemsControl x:Name="2st" Items="{Binding Path=. ,Mode=TwoWay}" ItemTemplate="{DynamicResource DataTemplate_Level2}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</DataTemplate>
</UserControl.Resources>
<ItemsControl Grid.Column="1" Items="{Binding MyCollection, Mode=TwoWay}" x:Name="lst" ItemTemplate="{DynamicResource DataTemplate_Level1}" Background="Gold"/>
My viewModel property
public ObservableCollection<ObservableCollection<bool>> MyCollection
{
get
{
return someCollection;
}
set
{
someCollection = value;
RaisePropertyChanged(nameof(MyCollection));
}
}
view of table
How Can I pass collection data changes to view model?
You need to declare a new class that will become viewmodel for checkbox with property of type book and proper RaisePropertyChanged invoking. And MyCollection must be collection of collections of instances of that class rather than bool
public class CheckboxViewModel
{
private bool _checkboxValue;
public bool CheckboxValue
{
get
{
return _checkboxValue;
}
set
{
_checkboxValue = value;
RaisePropertyChanged(nameof(CheckboxValue));
}
}
}
Make sure you have two-way binding in checkbox view to that property
BTW - RaisePropertyChanged at setter of MyCollection raises event with wrong property name in you example.
Related
I fill an ItemsControl with some keys list in a dictionary and I suggest to the user to map each key, printed in a Label, with some value in a ComboBox just below.
Finally, I have to get which Label key is corresponding by the selected value in the ComboBox in order to fill the dictionary values.
All my controls are binded to the ViewModel properties, in a MVVM pattern.
<ItemsControl x:Name="icMapping"
HorizontalAlignment="Center" MaxHeight="120"
ItemsSource="{Binding ColumnsMapping}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.Template>
<ControlTemplate>
<ScrollViewer VerticalScrollBarVisibility="Auto">
<ItemsPresenter/>
</ScrollViewer>
</ControlTemplate>
</ItemsControl.Template>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel HorizontalAlignment="Center" Margin="6">
<Label x:Name="lblPropertyKey"
Content="{Binding ApiPropertyKey}"
Width="250"/>
<ComboBox x:Name="cboxCsvHeaders"
Width="250"
ItemsSource="{Binding
RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType=Window},
Path=DataContext.CsvTemplateFile.CsvHeaders}"
SelectedItem="{Binding
RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType=Window},
Path=DataContext.SelectedCsvHeader, Mode=OneWayToSource}"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
I tried to loop through the ItemsTemplate in the ItemsControl, but I the ComboBox.Text is filled after binding the value in SelectedItem.
private string selectedCsvHeader;
public string SelectedCsvHeader
{
get => selectedCsvHeader;
set
{
selectedCsvHeader = value;
if (value != null)
{
var ic = this.configView.icMapping;
for (int i = 0; i < ic.Items.Count; i++)
{
System.Windows.Controls.ContentPresenter cp = (System.Windows.Controls.ContentPresenter)ic.ItemContainerGenerator.ContainerFromItem(ic.Items[i]);
System.Windows.Controls.ComboBox cbox = cp.ContentTemplate.FindName("cboxCsvHeaders", cp) as System.Windows.Controls.ComboBox;
System.Windows.Controls.Label lbl = cp.ContentTemplate.FindName("lblPropertyKey", cp) as System.Windows.Controls.Label;
MessageBox.Show((cbox.Text == selectedCsvHeader).ToString()); // False
}
FillDgPreview();
}
OnPropertyChanged(nameof(SelectedCsvHeader));
}
}
I used ItemsControl because I don't know in advance how much Keys I have in my dictionary. (After searching in the internet)
Here an image to explain
Thanks advance!
In order to set the values of the entries of a dictionary you would have to use a dictionary class that allows to change the Value property of its Key/Value pair entries.
Since Dictionary<TKey,TValue> does not allow that (because KeyValuePair<TKey,TValue> is immutable), you could write your own dictionary class, probably more sophisticated than in this example:
public class KeyValue
{
public KeyValue(string key, string value = null)
{
Key = key;
Value = value;
}
public string Key { get; }
public string Value { get; set; }
}
public class ViewModel
{
public List<KeyValue> Map { get; } = new List<KeyValue>();
public List<string> Keys { get; } = new List<string>();
}
You would now bind that view model as in the XAML shown below, where {Binding Key} and {Binding Value} use the dictionary entry (i.e. a Map collection element) as source object. These binding would look different in case you were using a custom dictionary class with a different entry type.
<ItemsControl ItemsSource="{Binding Map}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Key}"/>
<ComboBox SelectedItem="{Binding Value}"
ItemsSource="{Binding DataContext.Keys,
RelativeSource={RelativeSource AncestorType=ItemsControl}}"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
I've got this problem with Attached Properties in MVVM. I'm building a C# WPF application.
I'm filling a ObserverableCollection with custom Classes of the type ClassObject. And from this collection I'm creating ClassShapeViews on a canvas. These ClassShapeViews need a reference to the ClassObject to show correct information.
To pass the ClassObject reference i'm binding to a AttachedProperty in the XAML like this:
<ItemsControl ItemsSource="{Binding ClassObjectList}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas x:Name="canvas" Background="White" AllowDrop="True"
DragEnter="canvas_DragEnter"
DragOver="canvas_DragOver"
Drop="canvas_Drop"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<!-- This part doesn't work-->
<shape:ClassShapeView>
<Style TargetType="shape:ClassShapeView">
<Setter Property="shape:ClassShapeView.ClassObject" Value="{Binding}"/>
</Style>
</shape:ClassShapeView>
<Label Content="{Binding Name}"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
In the Code behind of the ClassShapeView I'm handeling the DependencyProperty:
public partial class ClassShapeView : UserControl
{
protected ClassShapeViewModel viewModel { get { return DataContext as ClassShapeViewModel; } }
public ClassShapeView()
{
InitializeComponent();
this.DataContext = new ClassShapeViewModel();
}
public static readonly DependencyProperty ClassObjectProperty =
DependencyProperty.RegisterAttached("ClassObject", typeof(ClassObject), typeof(ClassShapeView), new PropertyMetadata(default(ClassObject)));
public static void SetClassObject(UIElement element, ClassObject value)
{
element.SetValue(ClassObjectProperty, value);
}
public static ClassObject GetClassObject(UIElement element)
{
return (ClassObject)element.GetValue(ClassObjectProperty);
}
public ClassObject MyClassObject
{
get { return viewModel.ClassObject; }
set { viewModel.ClassObject = value; }
}
}
After receiving the ClassObject Reference I want to send this reference to the attached ViewModel with the property MyClassObject. The data in the ClassShapeView is bound to the ViewModel.
I just can't figure out how to to transform the ClassObject Reference form the CodeBehind to the ViewModel.
Thanks to Clemens i have the answer
"when the ItemsControl's ItemsSource property is bound to a collection of ClassObject instances (as what I suppose is your ClassObjectList), then the DataContext of each item container (e.g. a ContentPresenter) is set to the respective element from the collection. This DataContext is inherited into the ItemTemplate, so that the controls inside the DataTemplate (e.g. your UserControl) already get the correct ClassObject element as their DataContext."
So my XAML looks like this now
<ItemsControl ItemsSource="{Binding ClassObjectList}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas x:Name="canvas" Background="White" AllowDrop="True"
DragEnter="canvas_DragEnter"
DragOver="canvas_DragOver"
Drop="canvas_Drop"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<shape:ClassShapeView/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
And my View looks like this:
public partial class ClassShapeView : UserControl
{
public ClassShapeView()
{
InitializeComponent();
}
}
My XAML is as under. I have a main ViewModel which has a list of items and I want to display a property within this list
<ItemsControl ItemsSource="{Binding MyList}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Label Content="{Binding MyName, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"></Label>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
The problem is that MyName is always blank although my list has two items.
The main VM class has this property below and I add items in the constructor
public ObservableCollection<InnerViewModel> MyList { get; set; }
My inner VM has
public class InnerViewModel
{
private string _MyName;
public string MyName
{
get
{
return _MyName;
}
set
{
_MyName = value;
OnPropertyChanged("MyName");
}
}
I do have OnPropertyChanged in place but I'm not pasting it here for simplicity. I think the problem is with the XAML but I'm not sure. How do I get the property MyName to be displayed in my list of items in the view?
Since you use MyList as the ItemsSource, the data source for the child elements will be MyList. So you do not need to use the RelativeSource.
In other words, this should work :
<ItemsControl ItemsSource="{Binding MyList}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Label Content="{Binding MyName}"></Label>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Try and remove the relative source part of the binding.
<DataTemplate>
<Label Content="{Binding MyName}"></Label>
</DataTemplate>
Inside my class MainWindow I have:
public ObservableCollection<ViewModel> VMs ..
The MainWindow is constructed in the XAML (it creates an empty VMs in the class constructor too):
<Window.Resources>
<c:MainViewModel x:Key="ViewModelsSource"/>
</Window.Resources>
When I click on a button, I add ViewModel objects to the ObservableCollection VMs and the content of the ObservableCollection is shown in a ListBox:
<StackPanel DataContext="{Binding Source={StaticResource ViewModelsSource}}">
<ListBox IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding VMs}"
Background="Transparent"
HorizontalContentAlignment="Stretch"
> ...
The code for the Command Add is:
void AddListExecute()
{
VMs.Add(new ViewModel());
}
The constructor for ViewModel is:
public class ViewModel : MainViewModel
{
//Private Members
private ObservableCollection<FeeViewModel> _fees;
//Properties
public ObservableCollection<FeeViewModel> FVMs
{
get
{
return _fees;
}
set
{
_fees = value;
}
}
//Constructor
public ViewModel()
{
this._fees = new ObservableCollection<FeeViewModel>();
}
...
This part is working fine. Each ViewModel object contains another ObservableCollection:
public ObservableCollection<FeeViewModel> FVMs ..
I have a tabcontrol in the XAML that uses this ObservableCollection to do stuff:
<TabControl
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding FVMs, diag:PresentationTraceSources.TraceLevel=High}"
Style="{StaticResource EnabledTabs}" Grid.Column="1" Margin="0,0,10,0">
<TabControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
...
</StackPanel>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
...
EnabledTabs is a style that uses a property in FeeViewModel:
<Style TargetType="{x:Type TabControl}" x:Key="EnabledTabs">
<Setter Property="IsEnabled" Value="{Binding GotFees}"/>
</Style>
Now I have a binding error, FVMs is null and nothing is shown in the window. If I revert to a previous version without an ObservableCollection of ViewModel objects and I set the DataContext of the TabControl to that single ViewModel everything works fine.
How to set the DataContext of the TabControl to the dynamically created ViewModel objects?
Is it possible to do something like VMs/FVMs in the binding?
Thanks
Solved by adding DataContext to TabControl:
<TabControl
DataContext="{Binding VMs, Source={StaticResource ViewModelsSource}}"
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding FVMs, diag:PresentationTraceSources.TraceLevel=High}"
Style="{StaticResource EnabledTabs}" Grid.Column="1" Margin="0,0,10,0">
I'm writing simple WPF Application and I wanted to use ListView to display List of items. My code is:
WPF.xaml
<ListView Grid.Column="0" Grid.Row="1" Margin="10,0,10,5" ItemsSource="{Binding MyCollection.Elements}">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ElementDescriptions}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
WPF.xaml.cs
public MyViewModel ViewModel
{
get { return DataContext; }
set { DataContext = value; }
}
MyViewModel.cs
public OwnedCollection Elements { get; set; }
OwnedCollection.cs
public List<ElementDescriptions> ElementDescriptions { get; set; }
I'm 100% sure, that communication between View and ViewModel is correct, because displaying simple message doesn't make me troubles. Am I doing right binding in ListView?
A couple things:
First,
TextBlock Text="{Binding ElementDescriptions}"
doesn't make a lot of sense because ElementDescriptions is a collection. If you want to loop through all the ElementDescriptions in your List you should really be binding the ItemSource of the ListView to ElementDescriptions then accessing some text property of the ElementDescriptions class:
<ListView Grid.Column="0" Grid.Row="1" Margin="10,0,10,5" ItemsSource="{Binding MyCollection.ElementsElementDescriptions }">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ElementDescriptions.SomeTextField}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Second, are you using INotifyPropertyChanged so the view knows to update? More info on that here: OnPropertyChanged with a List