"EDIT"
What I want to achieve is something like -
MainWindow.xaml contains a Button, a ComboBox and a ContentControl.
UserControl-A and UserControl-C both contains a ContentControl.
UserControl-B and UserControl-D both contains a TextBlock.
UserControl-B will be Content of ContentControl Contained in
UserControl-A. UserControl-D will be Content of ContentControl
Contained in UserControl-C.
UserControl-A Or UserControl-C will be Content of ContentControl
Contained in MainWindow.xaml on click of the Button.
On change in SelectedValue of Combobox Update TextBlock of
UserControl-B Or UserControl-D.
"EDIT"
In addition to MainWindow.xaml, App.xaml and App.config I have MainContent1.xaml, MainContent2.xaml, ComboItems.cs and two subfolder Sub1 and Sub2, each of them contains a single file named S1.xaml, in my project.
All of my code is written in ComboItems.cs as -
namespace MultiBinding
{
public class Item
{
public string Name { get; set; }
public int Id { get; set; }
}
public class ComboItems
{
public ComboItems()
{
Items = new List<Item>(3);
for (int i = 1; i < 4; i++)
Items.Add(new Item { Id = i, Name = "Name " + i });
}
public List<Item> Items { get; set; }
}
public class ButtonContent : INotifyPropertyChanged
{
public ICommand MyCommand { get; set; }
private string _content;
public ButtonContent()
{
_content = "First";
MyCommand = new Command(Do, CanDo);
}
public string Content
{
get { return _content; }
set { _content = value; OnChange("Content"); }
}
private bool CanDo(object parameter) => true;
private void Do(object parameter) => Content = Content == "First" ? "Second" : "First";
public event PropertyChangedEventHandler PropertyChanged;
private void OnChange(string name) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
public class Command : ICommand
{
private Action<object> Do;
private Func<object, bool> CanDo;
public Command(Action<object> Do, Func<object, bool> CanDo)
{
this.Do = Do;
this.CanDo = CanDo;
}
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter) => true;
public void Execute(object parameter) => Do(parameter);
}
public class SomeText
{
public static string Text1 { get; set; }
public static string Text2 { get; set; }
}
public class Converter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
switch (values[1].ToString())
{
case "1": SomeText.Text1 = SomeText.Text2 = "Selected Item is 1"; break;
case "2": SomeText.Text1 = SomeText.Text2 = "Selected Item is 2"; break;
case "3": SomeText.Text1 = SomeText.Text2 = "Selected Item is 3"; break;
}
if (values[0].ToString() == "First") return new MainContent1();
else return new MainContent2();
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
Content of MainWindow.xaml is -
<Window x:Class="MultiBinding.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MultiBinding">
<Window.Resources>
<local:ComboItems x:Key="cboItems" />
<local:Item x:Key="Item" />
<local:Converter x:Key="convert" />
<local:ButtonContent x:Key="content"/>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="10*"/>
<RowDefinition Height="90*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="1*"/>
</Grid.ColumnDefinitions>
<ComboBox x:Name="combo"
DataContext="{Binding Source={StaticResource Item}}"
ItemsSource="{Binding Path=Items, Source={StaticResource cboItems}}"
DisplayMemberPath="Name"
SelectedValuePath="Id"
SelectedValue="{Binding Path=Id}"/>
<Button x:Name="butt" Grid.Column="1"
DataContext="{Binding Source={StaticResource content}}"
Content="{Binding Path=Content}"
Command="{Binding Path=MyCommand}"/>
<ContentControl Grid.Row="1" Grid.ColumnSpan="2">
<ContentControl.Content>
<MultiBinding Converter="{StaticResource convert}">
<Binding ElementName="butt" Path="Content"/>
<Binding ElementName="combo" Path="SelectedValue"/>
</MultiBinding>
</ContentControl.Content>
</ContentControl>
</Grid>
Content of MainContent1.xaml is -
<UserControl x:Class="MultiBinding.MainContent1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sub="clr-namespace:MultiBinding.Sub1">
<UserControl.Resources>
<sub:S1 x:Key="s1"/>
</UserControl.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock FontSize="20" Text="On Main ContentControl No. 1"/>
<ContentControl
Content="{Binding Source={StaticResource s1}}"
Grid.Row="1"/>
</Grid>
MainContent2.xaml contains exact same code as above except
xmlns:sub="clr-namespace:MultiBinding.Sub2"
Content of S1.xaml under folder Sub1 is -
<UserControl x:Class="MultiBinding.Sub1.S1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:main="clr-namespace:MultiBinding">
<UserControl.Resources>
<main:SomeText x:Key="MyText"/>
</UserControl.Resources>
<Grid Background="Bisque">
<TextBlock Text="{Binding Text1, Source={StaticResource MyText}}" />
</Grid>
S1.xaml under folder Sub2 is similar to that of Sub1 folder except
<TextBlock Text="{Binding Path=Text2, Source={StaticResource MyText}}" />
Everything works as expected in this way.
Is there any problem using static keyword infront of Text1 and Text2 properties of SomeText Class in ComboItems.cs?
How can I achieve same functionality without using static properties of SomeText class?
I have removed "ComboItems and Converter" classes from ComboItems.cs, renamed "ButtonContent" class as "MyCode" and implemented it as follows -
public class MyCode : INotifyPropertyChanged
{
//Fields
private Sub1.S1 _s1;
private Sub2.S1 _s2;
private MainContent1 _m1;
private MainContent2 _m2;
private object _mainContent;
private object _subContent;
private int _selectedIndex;
//Properties
public string Text { get; set; }
public List<Item> Items { get; set; }
public ICommand MyCommand { get; set; }
public string Content { get; set; }
public object MainContent
{
get { return _mainContent; }
set { _mainContent = value; OnChange("MainContent"); }
}
public object SubContent
{
get { return _subContent; }
set { _subContent = value; OnChange("SubContent"); }
}
public int SelectedIndex
{
get { return _selectedIndex; }
set
{
_selectedIndex = value;
//Action on selection change
switch (_selectedIndex)
{
case 0: Text = "Selected Item is 1"; break;
case 1: Text = "Selected Item is 2"; break;
case 2: Text = "Selected Item is 3"; break;
}
OnChange("Text");
}
}
//Constructor
public MyCode()
{
Content = "First";
MyCommand = new Command(Do, CanDo);
_m1 = new MainContent1();
_m2 = new MainContent2();
_s1 = new Sub1.S1();
_s2 = new Sub2.S1();
MainContent = _m1;
SubContent = _s1;
Items = new List<Item>(3);
for (int i = 1; i < 4; i++)
Items.Add(new Item { Id = i, Name = "Name " + i });
SelectedIndex = 0;
}
//Action on Button Click
private void Do(object parameter)
{
if(Content == "First")
{
MainContent = _m2;
SubContent = _s2;
Content = "Second";
}
else
{
MainContent = _m1;
SubContent = _s1;
Content = "First";
}
OnChange("Content");
}
private bool CanDo(object parameter) => true;
//Inotify
public event PropertyChangedEventHandler PropertyChanged;
private void OnChange(string name) =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
Changed MainWindow.xaml to -
<Window.Resources>
<local:MyCode x:Key="Code"/>
</Window.Resources>
<Grid DataContext="{Binding Source={StaticResource Code}}">
<Grid.RowDefinitions...>
<Grid.ColumnDefinitions...>
<ComboBox ItemsSource="{Binding Path=Items}"
DisplayMemberPath="Name"
SelectedValuePath="Id"
SelectedIndex="{Binding Path=SelectedIndex}"/>
<Button x:Name="butt" Grid.Column="1"
Content="{Binding Path=Content}"
Command="{Binding Path=MyCommand}"/>
<ContentControl Grid.Row="1" Grid.ColumnSpan="2"
Content="{Binding Path=MainContent}"/>
</Grid>
Both of MainContent1.xaml and MainContent2.xaml now contains -
<ContentControl Content="{Binding Path=SubContent}"/>
And S1.xaml under folder Sub1 and Sub2 contains -
<TextBlock Text="{Binding Path=Text}" />
Done!
Related
I am building WPF application where I am using MVVM pattern for data binding and command binding.
When I bind the tree view in a constructor(LoadTreeViewViewModel) it works without any issue but when I do the same in Button Click it is not working. I did a little bit of research where I am also binding to listview and binding is working without issue for listview on button click. So the problem is only with TreeView binding
Below is the complete code for the sample application.
XAML
<Window x:Class="WpfApp1.LoadTreeView"
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:WpfApp1"
mc:Ignorable="d"
Title="LoadTreeView" Height="300" Width="300">
<Window.DataContext>
<local:LoadTreeViewViewModel></local:LoadTreeViewViewModel>
</Window.DataContext>
<Window.Resources>
<local:LoadTreeViewViewModel x:Key="viewModel"></local:LoadTreeViewViewModel>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="5*"></RowDefinition>
<RowDefinition Height="5*"></RowDefinition>
<RowDefinition Height="1*"></RowDefinition>
</Grid.RowDefinitions>
<TreeView ItemsSource="{Binding Folders}" Grid.Column="0" Grid.Row="0">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Folders}" DataType="{x:Type local:IFolder}">
<TreeViewItem Header="{Binding FolderLabel}"/>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
<ListView VerticalAlignment="Top" Grid.Column="0" Grid.Row="1"
ItemsSource="{Binding Lists, Source={StaticResource viewModel}}">
<ListView.View>
<GridView>
<GridViewColumn/>
</GridView>
</ListView.View>
</ListView>
<Button Content="Button" HorizontalAlignment="Left" VerticalAlignment="Top" Width="75"
Grid.Column="0" Grid.Row="2"
Command="{Binding LoadSourceCommand, Source={StaticResource viewModel}}"/>
</Grid>
</Window>
LoadTreeView
public partial class LoadTreeView : Window
{
public LoadTreeView()
{
InitializeComponent();
//this.DataContext = new LoadTreeViewViewModel();
}
}
IFolder
public interface IFolder
{
List<IFolder> Folders { get; set; }
string FolderLabel { get; set; }
string FullPath { get; set; }
}
Folder
public class Folder : IFolder
{
public List<IFolder> Folders { get; set; }
public string FolderLabel { get; set; }
public string FullPath { get; set; }
public Folder()
{
Folders = new List<IFolder>();
}
}
LoadTreeViewViewModel
class LoadTreeViewViewModel : INotifyPropertyChanged
{
public LoadTreeViewViewModel()
{
this.LoadSourceCommand = new ViewModel.btnClick(this.LoadData, this.IsValid);
//this.Folders = await LoadDataAsync();
//LoadData();
}
private async void LoadData()
{
this.Folders = await LoadTreeAsync();
this.Lists = await LoadListAsync();
}
private async Task<string[]> LoadListAsync()
{
List<string> temp = new List<string>();
await Task.Delay(TimeSpan.FromSeconds(3)).ConfigureAwait(false);
//add Root items
temp.Add("Dummy1");
temp.Add("Dummy2");
temp.Add("Dummy3");
temp.Add("Dummy4");
return temp.ToArray();
}
private async Task<List<IFolder>> LoadTreeAsync()
{
List<IFolder> temp = new List<IFolder>();
await Task.Delay(TimeSpan.FromSeconds(3)).ConfigureAwait(false);
//add Root items
temp.Add(new Folder { FolderLabel = "Dummy1", FullPath = #"C:\dummy1" });
temp.Add(new Folder { FolderLabel = "Dummy2", FullPath = #"C:\dummy2" });
temp.Add(new Folder { FolderLabel = "Dummy3", FullPath = #"C:\dummy3" });
temp.Add(new Folder { FolderLabel = "Dummy4", FullPath = #"C:\dummy4" });
//add sub items
temp[0].Folders.Add(new Folder { FolderLabel = "Dummy11", FullPath = #"C:\dummy11" });
temp[0].Folders.Add(new Folder { FolderLabel = "Dummy12", FullPath = #"C:\dummy12" });
temp[0].Folders.Add(new Folder { FolderLabel = "Dummy13", FullPath = #"C:\dummy13" });
temp[0].Folders.Add(new Folder { FolderLabel = "Dummy14", FullPath = #"C:\dummy14" });
return temp;
}
private bool IsValid()
{
return true;
}
#region Members
private ViewModel.btnClick loadSourceCommand;
public btnClick LoadSourceCommand
{
get { return loadSourceCommand; }
set { loadSourceCommand = value; }
}
private List<IFolder> m_folders;
public List<IFolder> Folders
{
get { return m_folders; }
set
{
m_folders = value;
NotifiyPropertyChanged("Folders");
}
}
private string[] lists;
public string[] Lists
{
get { return lists; }
set { lists = value; NotifiyPropertyChanged("Lists"); }
}
#endregion
void NotifiyPropertyChanged(string property)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
public event PropertyChangedEventHandler PropertyChanged;
}
btnClick
public class btnClick : System.Windows.Input.ICommand
{
private Action WhatToExecute;
private Func<bool> WhenToExecute;
public btnClick(Action what, Func<bool> when)
{
WhatToExecute = what;
WhenToExecute = when;
}
public void Refresh()
{
if (this.CanExecuteChanged != null)
{
CanExecuteChanged(this, EventArgs.Empty);
}
}
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return WhenToExecute();
}
public void Execute(object parameter)
{
WhatToExecute();
}
}
You likely have a binding error.
Check the output log for and BindingExpression errors.
I suspect you want to use viewModel instance you've defined, rather than the window's datacontext, because they're going to be two different instances.
<TreeView ItemsSource="{Binding Folders, Source={StaticResource viewModel}}"
Grid.Column="0"
Grid.Row="0">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Folders}"
DataType="{x:Type local:IFolder}">
<TreeViewItem Header="{Binding FolderLabel}"/>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
I have an ItemsControl, which is bound to a collection, and I specify a HierarchicalDataTemplate to render the items. I got a template selector as well since the rendering for individual items are going to wary. For some reason this does not work.
Below is an extract of the code, can you please help? I'm looking to render items from the following hierarchy Parent->Child Collection->SubChildCollection (as in the below code).
<Window x:Class="WpfApplication2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:WpfApplication2="clr-namespace:WpfApplication2" Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<DataTemplate x:Key="EntityItemTemplate" DataType="{x:Type WpfApplication2:EntityItem}">
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
<WpfApplication2:TemplateSelector x:Key="ts" EntityItemTemplate="{StaticResource EntityItemTemplate}"/>
<HierarchicalDataTemplate x:Key="hdt" DataType="{x:Type WpfApplication2:EntityGroup}" ItemsSource="{Binding Path=EntityItems}" ItemTemplateSelector="{StaticResource ts}"/>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ItemsControl Grid.Row="0" ItemsSource="{Binding Path=Entity.EntityGroups}" ItemTemplate="{StaticResource hdt}"></ItemsControl>
</Grid>
</Window>
public partial class MainWindow : Window
{
ViewModel vm = new ViewModel();
public MainWindow()
{
InitializeComponent();
this.DataContext = vm;
vm.Entity = new Entity()
{
Name = "abc",
EntityGroups = new ObservableCollection<EntityGroup>()
{
new EntityGroup()
{
EntityItems = new ObservableCollection<EntityItem>()
{
new EntityItem() {Name = "Entity1"},
new EntityItem() {Name = "Entity2"}
}
}
}
};
}
}
public class TemplateSelector:DataTemplateSelector
{
public DataTemplate EntityItemTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
if (item == null || !(item is EntityItem))
return base.SelectTemplate(item, container);
return EntityItemTemplate;
}
}
public class ViewModel:NotificationObject
{
private Entity _entity;
public Entity Entity
{
get { return _entity; }
set
{
_entity = value;
RaisePropertyChanged(() => Entity);
}
}
}
public class Entity:NotificationObject
{
private string _name;
public string Name
{
get { return _name; }
set
{
_name = value;
RaisePropertyChanged(() => Name);
}
}
private ObservableCollection<EntityGroup> _entityGroups;
public ObservableCollection<EntityGroup> EntityGroups
{
get { return _entityGroups; }
set
{
_entityGroups = value;
RaisePropertyChanged(() => EntityGroups);
}
}
}
public class EntityGroup:NotificationObject
{
public string Name { get; set; }
private ObservableCollection<EntityItem> _entityItems;
public ObservableCollection<EntityItem> EntityItems
{
get { return _entityItems; }
set
{
_entityItems = value;
RaisePropertyChanged(() => EntityItems);
}
}
}
public class EntityItem:NotificationObject
{
private string _name;
public string Name
{
get { return _name; }
set
{
_name = value;
RaisePropertyChanged(() => Name);
}
}
}
Because you're using it in an ItemsControl, you shouldn't use a HierarchicalDataTemplate. As MSDN states, a HierarchicalDataTemplate:
Represents a DataTemplate that supports HeaderedItemsControl, such as
TreeViewItem or MenuItem.
An ItemsControl shows its data inside a ContentPresenter. A TreeView will generate TreeViewItems, and a Menu will generate MenuItems.
If you want to use a DataTemplateSelector to display different templates for items in the ItemsControl, just set it directly as the ItemTemplateSelector:
<ItemsControl ItemsSource="{Binding Path=Entity.EntityGroups}"
ItemTemplateSelector="{StaticResource ts}" />
If you want a hierarchical display of your data, use a TreeView:
<TreeView ItemsSource="{Binding Path=Entity.EntityGroups}"
ItemTemplate="{StaticResource hdt}" />
I have MyCollection of MyPOCO object (that has two string properties).
When I try to implement a HierarchicalDataTemplate into a treeview the binding is not working, I get the class name.
I know that if i take out the datatemplate from the control everything works fine but i am interested to see why this example is not working.
<Grid x:Name="LayoutRoot" Background="White" DataContext="{Binding Source={StaticResource testVM}}">
<sdk:TreeView Margin="8,8,8,111" ItemsSource="{Binding MyCollection}">
<sdk:HierarchicalDataTemplate ItemsSource="{Binding MyPOCO}">
<sdk:HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Property1}"/>
<TextBlock Text="{Binding Property2}"/>
</StackPanel>
</DataTemplate>
</sdk:HierarchicalDataTemplate.ItemTemplate>
</sdk:HierarchicalDataTemplate>
</sdk:TreeView>
</Grid>
Here is the ViewModel also.
namespace MyPOCProject
{
public class MyPOCO
{
private string property1;
public string Property1
{
get { return property1; }
set { property1 = value; }
}
private string property2;
public string Property2
{
get { return property2; }
set { property2 = value; }
}
public MyPOCO(string p1, string p2)
{
property1 = p1;
property2 = p2;
}
}
public class MyViewModel : INotifyPropertyChanged
{
private ObservableCollection<MyPOCO> myCollection;
public ObservableCollection<MyPOCO> MyCollection
{
get { return myCollection; }
set { myCollection = value; RaisePropertyChanged("MyCollection"); }
}
public MyViewModel()
{
MyPOCO _poco1 = new MyPOCO("aaa1", "bbb1");
MyPOCO _poco2 = new MyPOCO("aaa2", "bbb2");
MyPOCO _poco3 = new MyPOCO("aaa3", "bbb3");
MyCollection = new ObservableCollection<MyPOCO>() { _poco1, _poco2, _poco3 };
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
So what am I doing wrong?
AGAIN ... I am interested in this particular example. I want to know what's wrong with this example and why.
Thanks.
The code you posted is not hierarchical, In other words: The MyPOCO Class is not containing a property MyCollection<MYPOCO> Children.
Here is an example for the HierarchicalDataTemplate
Xaml:
<sdk:TreeView x:Name="MyTreeView"
HorizontalAlignment="Left"
ItemsSource="{Binding MyCollection}"
Width="200" Height="280">
<sdk:TreeView.ItemTemplate>
<sdk:HierarchicalDataTemplate ItemsSource="{Binding Path=Children}">
<TextBlock Text="{Binding Path=Header}"/>
<sdk:HierarchicalDataTemplate.ItemTemplate>
<sdk:HierarchicalDataTemplate ItemsSource="{Binding Path=Children}">
<TextBlock Text="{Binding Path=Header}"/>
</sdk:HierarchicalDataTemplate>
</sdk:HierarchicalDataTemplate.ItemTemplate>
</sdk:HierarchicalDataTemplate>
</sdk:TreeView.ItemTemplate>
</sdk:TreeView>
Codebehind classes:
public class MyPoco
{
public MyPoco(string header, int sampleChildrenCount)
{
this.Header = header;
this.Children = new ObservableCollection<MyPoco>();
if (sampleChildrenCount > 0)
{
for (int i = 0; i < sampleChildrenCount; i++)
{
string newHeader = String.Format("Test {0}", sampleChildrenCount * i);
var myPoco = new MyPoco(newHeader, sampleChildrenCount - 1)
this.Children.Add(myPoco);
}
}
}
public string Header { get; set; }
public ObservableCollection<MyPoco> Children { get; set; }
}
public class MyViewModel
{
public MyViewModel()
{
MyCollection = new ObservableCollection<MyPoco>();
for (int i = 0; i < 6; i++)
{
this.MyCollection.Add(new MyPoco(String.Format("Test {0}", i), 5));
}
}
public ObservableCollection<MyPoco> MyCollection { get; set; }
}
Codebehind startup:
public MainPage()
{
public MainPage()
{
InitializeComponent();
MyTreeView.DataContext = new MyViewModel();
}
}
I've just stuck in a problem to bind collection in ItemsControl with ItemTeplate that contains bounded ComboBox.
In my scenario I need to "generate" form that includes textbox and combobox for each item in collection and let user to update items. I could use DataGrid for that but I'd like to see all rows in edit mode, so I use ItemsControl with custom ItemTemplate.
It's ok to edit textboxes but when you try to change any ComboBox, all other ComboBoxes in other rows will change too.
Is it a bug or feature?
Thanks, Ondrej
Window.xaml
<Window x:Class="ComboInItemsControlSample.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="480" Width="640">
<Window.Resources>
<CollectionViewSource x:Key="cvsComboSource"
Source="{Binding Path=AvailableItemTypes}" />
<DataTemplate x:Key="ItemTemplate">
<Border BorderBrush="Black" BorderThickness="0.5" Margin="2">
<Grid Margin="3">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="20" />
<ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
<TextBox Grid.Column="0" Text="{Binding Path=ItemValue}" />
<ComboBox Grid.Column="2"
SelectedValue="{Binding Path=ItemType}"
ItemsSource="{Binding Source={StaticResource cvsComboSource}}"
DisplayMemberPath="Name"
SelectedValuePath="Value" />
</Grid>
</Border>
</DataTemplate>
</Window.Resources>
<Grid>
<ItemsControl ItemsSource="{Binding Path=SampleItems}"
ItemTemplate="{StaticResource ItemTemplate}"
Margin="10" />
</Grid>
Window.xaml.cs
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
DataContext = new ViewModel();
}
}
public class ViewModel
{
public ViewModel()
{
SampleItems = new List<SampleItem> {
new SampleItem { ItemValue = "Value 1" },
new SampleItem { ItemValue = "Value 2" },
new SampleItem { ItemValue = "Value 3" }
};
AvailableItemTypes = new List<SampleItemType> {
new SampleItemType { Name = "Type 1", Value = 1 },
new SampleItemType { Name = "Type 2", Value = 2 },
new SampleItemType { Name = "Type 3", Value = 3 },
new SampleItemType { Name = "Type 4", Value = 4 }
};
}
public IList<SampleItem> SampleItems { get; private set; }
public IList<SampleItemType> AvailableItemTypes { get; private set; }
}
public class SampleItem : ObservableObject
{
private string _itemValue;
private int _itemType;
public string ItemValue
{
get { return _itemValue; }
set { _itemValue = value; RaisePropertyChanged("ItemValue"); }
}
public int ItemType
{
get { return _itemType; }
set { _itemType = value; RaisePropertyChanged("ItemType"); }
}
}
public class SampleItemType : ObservableObject
{
private string _name;
private int _value;
public string Name
{
get { return _name; }
set { _name = value; RaisePropertyChanged("Name"); }
}
public int Value
{
get { return _value; }
set { _value = value; RaisePropertyChanged("Value"); }
}
}
public abstract class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName) {
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
Picture
here you can see the result on picture
I believe it's because you're binding to a CollectionViewSource, which tracks the current item. Try binding directly to your list instead, which won't track the current item
<ComboBox Grid.Column="2"
SelectedValue="{Binding Path=ItemType}"
DisplayMemberPath="Name"
SelectedValuePath="Value"
ItemsSource="{Binding RelativeSource={
RelativeSource AncestorType={x:Type ItemsControl}},
Path=DataContext.AvailableItemTypes}" />
While you have a combobox in each row, it doesnt see these comboboxes as being seperate. i.e. They are all using the same collection, and the same selectedValue, so when a value changes in one box, it changes in all of them.
The best way to fix this is to add the SampleItemType collection as a property on your SampleItem model and to then bind the combo box to that property.
This code example shows how to get the position of the scrollbar in a ScrollViewer (with ScrollToVerticalOffset) so that e.g. you can reset the scrollbar to this position if you need to recreate it.
Is there any way to do this for a TreeView control, i.e. get a collection of node indexes which the user has expanded?
The easiest way to do this is to bind TreeViewItem.IsExpanded property to the ViewModel, and then go through model and calculate.
I wrote an example for you. It calculates number of expanded nodes, but you can do whatever you want with expanded guys...
C#:
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;
namespace WpfApplication1
{
public partial class TestBrowser : Window
{
private TreeViewItemViewModel[] _items;
public TestBrowser()
{
InitializeComponent();
var item1 = new TreeViewItemViewModel();
var item2 = new TreeViewItemViewModel();
var item3 = new TreeViewItemViewModel();
item3.Children.Add(new TreeViewItemViewModel());
item3.Children.Add(new TreeViewItemViewModel());
var child3 = new TreeViewItemViewModel();
child3.Children.Add(new TreeViewItemViewModel());
item3.Children.Add(child3);
_items = new[] {item1, item2, item3};
tv.DataContext = _items;
}
private void CalculateExpandedClick(object sender, RoutedEventArgs e)
{
var expanded = 0;
foreach (TreeViewItemViewModel item in _items)
{
expanded += GetNumberOfExpanded(item);
}
ExpandedNumber.Text = expanded.ToString();
}
private int GetNumberOfExpanded(TreeViewItemViewModel model)
{
var expandedCount = 0;
if (model.IsExpanded)
{
expandedCount += 1;
foreach (TreeViewItemViewModel child in model.Children)
{
expandedCount += GetNumberOfExpanded(child);
}
}
return expandedCount;
}
}
/// <summary>
/// Single tree view item view model.
/// </summary>
public class TreeViewItemViewModel : INotifyPropertyChanged
{
public ObservableCollection<TreeViewItemViewModel> Children
{
get; private set;
}
private bool _isExpanded;
private string _text;
public bool IsExpanded
{
get { return _isExpanded; }
set
{
_isExpanded = value;
OnPropertyChanged("IsExpanded");
}
}
public string Text
{
get { return _text; }
set
{
_text = value;
OnPropertyChanged("Text");
}
}
public TreeViewItemViewModel()
{
Children = new ObservableCollection<TreeViewItemViewModel>();
Text = DateTime.Now.ToLongTimeString(); // Just fake data.
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string name)
{
var changed = PropertyChanged;
if (changed != null)
{
changed(this, new PropertyChangedEventArgs(name));
}
}
}
}
XAML:
<Window x:Class="WpfApplication1.TestBrowser"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Expanded Test"
Height="300"
Width="300">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TreeView x:Name="tv"
ItemsSource="{Binding}">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Text}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
<StackPanel Grid.Column="1">
<Button Content="Get number of expanded items"
Click="CalculateExpandedClick"/>
<TextBlock x:Name="ExpandedNumber" />
</StackPanel>
</Grid>
</Window>
Hope this helps.