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>
Related
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.
I want to build a dynanmic list of buttons (or whatever element is in the template, doesn't really matter.., my XAML is:
<Button Content="Add New Button" VerticalAlignment="Center" Click="AddNewButton" HorizontalAlignment="Center"/>
<ItemsControl ItemsSource="{Binding ButtonsList}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Height="40" MinWidth="40" Content="{Binding Name}" Margin="2"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
My code behind looks like this:
public MainWindow()
{
InitializeComponent();
ButtonsList = new ObservableCollection<ShortcutButton>();
}
public class ShortcutButton
{
public string Name { get; set; }
}
public ObservableCollection<ShortcutButton> ButtonsList { get; set; }
private void AddNewButton(object sender, RoutedEventArgs e)
{
tblock.Text += "Button Clicked" + Environment.NewLine;
ButtonsList.Add(new ShortcutButton() { Name = "New button title" });
}
When I debug, I can see that objects are being added to the ButtonsList Observable Collection, but it does not update in the bound WrapPanel in ItemsControl.. What am I doing wrong?
Your Binding is incorrect, by default it will try to search for a property named ButtonList in the DataContext. To bind your ItemsSource to your ButtonList defined in the MainWindow you should change the RelativeSource like this:
<ItemsControl ItemsSource="{Binding Path=ButtonsList, RelativeSource={RelativeSource AncestorType={x:Type local:MainWindow}}}">
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>
I'm attempting to databind to a Windows Phone 8 Toolkit Expander view with the following XAML and C# class. I know that the DataContext is set properly because the Headers have the proper text. However, the rest of the items aren't set properly (except for the ExpanderTemplate)
<phone:PanoramaItem Header="Skill Sheet">
<ListBox Name="SkillSheet" ItemsSource="{Binding}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<toolkit:ExpanderView Header="{Binding}"
ItemsSource="{Binding}"
IsNonExpandable="False">
<toolkit:ExpanderView.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding groupName}" FontFamily="{StaticResource PhoneFontFamilySemiBold}" LineHeight="{StaticResource LongListSelectorGroupHeaderFontSize}" />
</DataTemplate>
</toolkit:ExpanderView.HeaderTemplate>
<toolkit:ExpanderView.ExpanderTemplate>
<DataTemplate>
<TextBlock Text="Test" />
</DataTemplate>
</toolkit:ExpanderView.ExpanderTemplate>
<!--This is the area that is not getting databound-->
<toolkit:ExpanderView.ItemTemplate>
<DataTemplate>
<ListBox ItemsSource="{Binding skillNames}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding skill}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DataTemplate>
</toolkit:ExpanderView.ItemTemplate>
</toolkit:ExpanderView>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</phone:PanoramaItem>
And here are the classes that the XAML is getting bound to:
public class TreeMapSkill
{
public string skill { get; set; }
}
public class TreeMapping
{
public string groupName { get; set; }
public List<TreeMapSkill> skillNames { get; set; }
public TreeMapping()
{
skillNames = new List<TreeMapSkill>();
}
}
public class TreeMappingList
{
public List<TreeMapping> mapping { get; set; }
public TreeMappingList() { }
public TreeMappingList(Dictionary<string, List<string>> map)
: base()
{
this.mapping = new List<TreeMapping>();
foreach (string key in map.Keys)
{
TreeMapping tMap = new TreeMapping();
tMap.groupName = key;
foreach (string val in map[key])
tMap.skillNames.Add(new TreeMapSkill() { skill = val });
this.mapping.Add(tMap);
}
}
The Dictionary in the constructor is simply a list of skills associated to a specific group. I can also provide a sample object if it's needed for additional reference.
Why are you adding a ListBox inside the Expander's ItemTemplate? It is already a controls collection so you don't need a ListBox in there. Just put your DataTemplate inside.
<toolkit:ExpanderView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding skill}" />
</DataTemplate>
</toolkit:ExpanderView.ItemTemplate>
The second thing is you need to specify the property path on the binding of the ItemSource property for the expander.
<toolkit:ExpanderView Header="{Binding}"
ItemsSource="{Binding skillNames}"
IsNonExpandable="False">
I am trying to bind a list of string values to a listbox so that their values are listed line by line. Right now I use this:
<ListBox Margin="20" ItemsSource="{Binding Path=PersonNames}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Id}"></TextBlock>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
But I don't know what I am supposed to put into the textblock, instead of Id, since they are all string values, not custom classes.
Also it complains not having to find the PersonNames when I have it inside MainPage, as MainPage.PersonNames.
I set the data context to:
DataContext="{Binding RelativeSource={RelativeSource Self}}"
I am doing it wrong?
If simply put that your ItemsSource is bound like this:
YourListBox.ItemsSource = new List<String> { "One", "Two", "Three" };
Your XAML should look like:
<ListBox Margin="20" Name="YourListBox">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Update:
This is a solution when using a DataContext. Following code is the viewmodel you will be passing to the DataContext of the page and the setting of the DataContext:
public class MyViewModel
{
public List<String> Items
{
get { return new List<String> { "One", "Two", "Three" }; }
}
}
//This can be done in the Loaded event of the page:
DataContext = new MyViewModel();
Your XAML now looks like this:
<ListBox Margin="20" ItemsSource="{Binding Items}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The advantage of this approach is that you can put a lot more properties or complex objects in the MyViewModel class and extract them in the XAML. For example to pass a List of Person objects:
public class ViewModel
{
public List<Person> Items
{
get
{
return new List<Person>
{
new Person { Name = "P1", Age = 1 },
new Person { Name = "P2", Age = 2 }
};
}
}
}
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
And the XAML:
<ListBox Margin="20" ItemsSource="{Binding Items}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Name}" />
<TextBlock Text="{Binding Path=Age}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
You should show us the code for PersonNames, and I am not sure I understand your question, but maybe you want to bind it like this:
<TextBlock Text="{Binding Path=.}"/>
or
<TextBlock Text="{Binding}"/>
This will bind to the current element in the list (assuming PersonNames is a list of strings). Otherwise, you will see the class name in the list.
If the items source is enumerable as string-entries, use the following:
<TextBlock Text="{Binding}"></TextBlock>
You can use this syntax on any object. Generally, the ToString() -method will then called to get the value. This is in many cases very handy. But beware that no change notification will occur.
You can do this without having to explicitly define the TextBlock control as a part of your ListBox (unless you want better formatting). The trick to getting the binding to trigger is using an ObservableCollection<string> instead of List<string>
Window1.xaml
<ListView Width="250" Height="50" ItemsSource="{Binding MyListViewBinding}"/>
Window1.xaml.cs
public Window1()
{
InitializeComponent();
DataContext = this;
// Need to initialize this, otherwise you get a null exception
MyListViewBinding = new ObservableCollection<string>();
}
public ObservableCollection<string> MyListViewBinding { get; set; }
// Add an item to the list
private void Button_Click_Add(object sender, RoutedEventArgs e)
{
// Custom control for entering a single string
SingleEntryDialog _Dlg = new SingleEntryDialog();
// OutputBox is a string property of the custom control
if ((bool)_Dlg.ShowDialog())
MyListViewBinding.Add(_Dlg.OutputBox.Trim());
}