HierarchicalDataTemplate Binding not working - c#

I want to create a 2 level treview, with the root element of a string "Items":
-Items
-item1
-item2
-...
I have 2 classes to achieve this: ItemList, Item. I call them with the property CurrentItems.
private ItemList _currentItems = null;
public ItemList CurrentItems
{
get
{
return _currentItems;
}
set
{
if(_currentItems!=value)
{
SetProperty(ref _currentItems,
value, () => CurrentItems);
}
}
}
CurrentItems is initialized by creating a temporary ItemsList which is then filled with Items:
ItemList itemTempList = new ItemList();
...
while(...)
{
Item item = new Item();
...
itemTempList.Items.Add(item);
}
CurrentItems = itemTempList;
These are the two classes used:
public class Item
{
public long ObjectID { get; set; }
public string ItemName { get; set; }
}
public class ItemList : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private ObservableCollection<Item> items = new ObservableCollection<Item>();
public ObservableCollection<Item> Items
{
get
{
return items;
}
set
{
items = value;
OnPropertyChanged();
}
}
private ObservableCollection<string> types = new ObservableCollection<string>();
public ObservableCollection<string> Types
{
get
{
if(types.Count > 0)
{
types.Add("Items");
}
return types;
}
set
{
types = value;
OnPropertyChanged();
}
}
protected void OnPropertyChanged([CallerMemberName] string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
The View is initialized with a constructor:
public ItemView()
{
InitializeComponent();
}
And finally, this is the xaml:
<TreeView Name="itemTree" Grid.Row="1" Grid.Column="0" SelectedItemChanged="itemTree_SelectedItemChanged"
ItemsSource="{Binding CurrentItems}">
<TreeView.Resources>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="True"/>
</Style>
</TreeView.Resources>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Items,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}">
<Label Content="{Binding Types,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}"/>
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ItemName,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}">
<TextBlock.ContextMenu>
<ContextMenu>
<MenuItem Header="Duplicate" Click="DuplicateCell"/>
<MenuItem Header="Delete" Click="DeleteCell"/>
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
With my current understanding and code I am not able to create the TreeView.
Any help would be greatly appreciated!

Related

Dynamically changing tabitem via button command not working wfp

Im trying to create a function in a wpf program, where I can select an item in a listview, and press a button and it changes the tabitem and allows me to then edit the item from the listview that was selected. Im having issues with getting the tabitem to change for me.
For the navigation of my app, I have a ViewModelBase, which my AppointmentsViewModel inherits from. Inside the AppointmentsViewVM there is a tabcontrol with 4 items, by clicking each one it loads the requested view/viewmodel for that function.
This is not the only way I've tried to get this to work, Im currently on day 4. I could get the TabIndex to change in the TabControl earlier, but the tab would still not change for me. So I abandoned that and tried the below route (still no luck).
ViewModelBase
namespace MBR2.ViewModels
{
public class ViewModelBase : INotifyPropertyChanged
{
public ICommand MainMenuViewDogs_Command { get; set; }
public ICommand MainMenuViewAppointments_Command { get; set; }
private object _SelectedViewModel;
public object SelectedViewModel
{
get { return _SelectedViewModel; }
set
{
_SelectedViewModel = value;
OnPropertyChanged("SelectedViewModel");
}
}
public ViewModelBase()
{
MainMenuViewDogs_Command = new BaseCommand(OpenDogs);
MainMenuViewAppointments_Command = new BaseCommand(OpenAppointments);
}
private void OpenDogs(object obj)
{
SelectedViewModel = new DogsViewModel();
}
private void OpenAppointments(object obj)
{
SelectedViewModel = new AppointmentsViewModel();
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private bool _SelectedIndexView;
public bool SelectedIndexView
{
get { return _SelectedIndexView; }
set
{
_SelectedIndexView = value;
OnPropertyChanged("SelectedIndexView");
}
}
private bool _SelectedIndexAdd;
public bool SelectedIndexAdd
{
get { return _SelectedIndexAdd; }
set
{
_SelectedIndexView = value;
OnPropertyChanged("SelectedIndexAdd");
}
}
private bool _SelectedIndexEdit;
public bool SelectedIndexEdit
{
get { return _SelectedIndexEdit; }
set
{
_SelectedIndexView = value;
OnPropertyChanged("SelectedIndexEdit");
}
}
private bool _SelectedIndexDelete;
public bool SelectedIndexDelete
{
get { return _SelectedIndexDelete; }
set
{
_SelectedIndexView = value;
OnPropertyChanged("SelectedIndexDelete");
}
}
}
}
AppointmentsViewModel
{
public class AppointmentsViewModel : ViewModelBase
{
private AppointmentsAddVM _AppointmentsAddVM;
public AppointmentsAddVM AppointmentsAddVM { get { return _AppointmentsAddVM; } }
private AppointmentsEditVM _AppointmentsEditVM;
public AppointmentsEditVM AppointmentsEditVM { get { return _AppointmentsEditVM; } }
private AppointmentsDeleteVM _AppointmentsDeleteVM;
public AppointmentsDeleteVM AppointmentsDeleteVM { get { return _AppointmentsDeleteVM; } }
private AppointmentsViewVM _AppointmentsViewVM;
public AppointmentsViewVM AppointmentsViewVM { get { return _AppointmentsViewVM; } }
public ObservableCollection<object> ViewModelList { get; set; }
public AppointmentsViewModel()
{
this.ViewModelList = new ObservableCollection<object>();
_AppointmentsAddVM = new AppointmentsAddVM();
_AppointmentsEditVM = new AppointmentsEditVM();
_AppointmentsDeleteVM = new AppointmentsDeleteVM();
_AppointmentsViewVM = new AppointmentsViewVM();
this.ViewModelList.Add(_AppointmentsAddVM);
this.ViewModelList.Add(_AppointmentsEditVM);
this.ViewModelList.Add(_AppointmentsDeleteVM);
this.ViewModelList.Add(_AppointmentsViewVM);
}
}
}
AppointmentsView.xaml
<UserControl
x:Class="MBR2.Views.AppointmentsView"
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:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
xmlns:vms="clr-namespace:MBR2.ViewModels.Appointments"
xmlns:views="clr-namespace:MBR2.Views.Appointments"
xmlns:viewmodels="clr-namespace:MBR2.ViewModels"
d:DataContext="{d:DesignInstance Type=viewmodels:AppointmentsViewModel}"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.Resources>
<DataTemplate DataType="{x:Type vms:AppointmentsViewVM}">
<views:AppointmentsViewView />
</DataTemplate>
<DataTemplate DataType="{x:Type vms:AppointmentsAddVM}">
<views:AppointmentsAddView />
</DataTemplate>
<DataTemplate DataType="{x:Type vms:AppointmentsDeleteVM}">
<views:AppointmentsDeleteView />
</DataTemplate>
<DataTemplate DataType="{x:Type vms:AppointmentsEditVM}">
<views:AppointmentsEditView />
</DataTemplate>
</UserControl.Resources>
<Grid x:Name="Appointments" Width="Auto" Height="Auto">
<DockPanel HorizontalAlignment="Center"
Height="Auto"
LastChildFill="False"
VerticalAlignment="Top"
Width="Auto">
<TabControl x:Name="VMTabControl">
<TabItem x:Name="ViewTab"
TabIndex="0"
Header="View"
IsSelected="{Binding SelectedIndexView}"
Content="{Binding AppointmentsViewVM}"></TabItem>
<TabItem x:Name="AddTab"
TabIndex="1"
Header="Add"
IsSelected="{Binding SelectedIndexAdd}"
Content="{Binding AppointmentsAddVM}"></TabItem>
<TabItem x:Name="EditTab"
TabIndex="2"
Header="Edit"
IsSelected="{Binding SelectedIndexEdit}"
Content="{Binding AppointmentsEditVM}"></TabItem>
<TabItem x:Name="DeleteTab"
TabIndex="3"
Header="Delete"
IsSelected="{Binding SelectedIndexDelete}"
Content="{Binding AppointmentsDeleteVM}"></TabItem>
</TabControl>
</DockPanel>
</Grid>
</UserControl>
And the associated AppointmentsViewVM
namespace MBR2.ViewModels.Appointments
{
public class AppointmentsViewVM : ViewModelBase, INotifyPropertyChanged
{
private List<AppointmentsView_Wrapper> _AppointmentsView;
public List<AppointmentsView_Wrapper> AppointmentsView
{
get { return _AppointmentsView; }
set
{
_AppointmentsView = value;
OnPropertyChanged("AppointmentsView");
}
}
private List<string> _NameColumn = new List<string>();
public List<string> NameColumn
{
get { return _NameColumn; }
set
{
_NameColumn = value;
OnPropertyChanged("NameColumn");
}
}
private List<string> _ApptDateColumn = new List<string>();
public List<string> ApptDateColumn
{
get { return _ApptDateColumn; }
set
{
_ApptDateColumn = value;
OnPropertyChanged("ApptDateColumn");
}
}
private List<string> _ApptTimeColumn = new List<string>();
public List<string> ApptTimeColumn
{
get { return _ApptTimeColumn; }
set
{
_ApptTimeColumn = value;
OnPropertyChanged("ApptTimeColumn");
}
}
private List<string> _ApptVetColumn = new List<string>();
public List<string> ApptVetColumn
{
get { return _ApptVetColumn; }
set
{
_ApptVetColumn = value;
OnPropertyChanged("ApptVetColumn");
}
}
private List<string> _ApptCreatedColumn = new List<string>();
public List<string> ApptCreatedColumn
{
get { return _ApptCreatedColumn; }
set
{
_ApptCreatedColumn = value;
OnPropertyChanged("ApptCreatedColumn");
}
}
private List<int> _ApptIDColumn = new List<int>();
public List<int> ApptIDColumn
{
get { return _ApptIDColumn; }
set
{
_ApptIDColumn = value;
OnPropertyChanged("ApptIDColumn");
}
}
private string _AppointmentEdit_Enabled = "False";
public string AppointmentEdit_Enabled
{
get { return _AppointmentEdit_Enabled; }
set
{
_AppointmentEdit_Enabled = value;
OnPropertyChanged("AppointmentEdit_Enabled");
}
}
private AppointmentsView_Wrapper _ApptIDSelected;
public AppointmentsView_Wrapper ApptIDSelected
{
get { return _ApptIDSelected; }
set
{
AppointmentEdit_Enabled = "True";
_ApptIDSelected = value;
OnPropertyChanged("ApptIDSelected");
}
}
public AppointmentData AppointmentData = new AppointmentData();
public Messaging Messaging = new Messaging();
public ICommand AppointmentsListView_Command => new DelegateCommand<object>(AppointmentsListView_Clicked);
public ICommand EditSelection_Command => new DelegateCommand<object>(EditSelection_Clicked);
public AppointmentsViewVM()
{
BuildPage();
}
public async void BuildPage()
{
AppointmentsView = await AppointmentData.Appointments_GetAll();
foreach(var item in AppointmentsView)
{
ApptIDColumn.Add(item.ApptID);
NameColumn.Add(item.DogName);
ApptDateColumn.Add(item.ApptDate);
ApptTimeColumn.Add(item.ApptTime);
ApptVetColumn.Add(item.ApptVet);
ApptCreatedColumn.Add(item.ApptCreated.ToString("dd/mm/yyyy"));
}
}
public void AppointmentsListView_Clicked(object obj)
{
Messaging.ShowAlert(ApptIDSelected.ApptID.ToString());
}
public void EditSelection_Clicked(object obj)
{
bool result = Messaging.AskQuestion(ApptIDSelected.ApptID.ToString());
if(result)
{
SelectedIndexView = false;
SelectedIndexAdd = false;
SelectedIndexEdit = true;
SelectedIndexDelete = false;
OnPropertyChanged("SelectedIndexView");
OnPropertyChanged("SelectedIndexAdd");
OnPropertyChanged("SelectedIndexEdit");
OnPropertyChanged("SelectedIndexDelete");
}
else
{
Messaging.ShowAlert("no");
}
}
}
}
Here's a minimal reproduction of something where you select in a listbox and that then selects a corresponding tab in a tabcontrol.
This is very minimal but we can perhaps imagine a more sophisticated viewmodel per item in the listbox with name and viewmodel or something.
This is mainwindow.
<Window.Resources>
<DataTemplate DataType="{x:Type local:Avm}">
<local:Aview/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:Bvm}">
<local:Bview/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:Cvm}">
<local:Cview/>
</DataTemplate>
</Window.Resources>
<Window.DataContext>
<local:MainWindowViewmodel/>
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<ListBox ItemsSource="{Binding ViewModels}"
x:Name="lb"
>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ViewModelName}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<TabControl Grid.Column="1"
ItemsSource="{Binding ViewModels}"
SelectedItem="{Binding ElementName=lb, Path=SelectedItem, Mode=TwoWay}"
>
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock
Text="{Binding ViewModelName}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<ContentPresenter Content="{Binding}"/>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>
I have only done 3 views and viewmodels.
Note that the selecteditem of the listbox is bound twoway to the tabcontrol.
I have matching views and viewmodels A, B and C
MainWindowViewModel
public class MainWindowViewmodel : ViewModelBase
{
public ObservableCollection<Object> ViewModels { get; set; } = new ObservableCollection<Object>{
new Avm{ViewModelName="A viewmodel" },
new Bvm{ViewModelName="B viewmodel" },
new Cvm{ViewModelName="C viewmodel" }
};
}
Both the itemssource of listbox and tabcontrol are bound to that collection of viewmodels. Which are, as I mentioned, as simple as you get really.
Viewmodelbase
public class ViewModelBase : INotifyPropertyChanged
{
public string ViewModelName { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
Avm, Bvm and Cvm just inherit from that.
An example usercontrol view.
<Grid>
<TextBlock Text="{Binding ViewModelName}"/>
</Grid>
</UserControl>
When I spin that up, select and select an item in the listbox the matching tab is selected. And vice versa. Select a tab and it selects the same one in the listbox.

Maintain checkbox state while the app is running in WPF

I'm working in MVVM, WPF and I have a popup; inside this popup is a listbox and inside the listbox I have a checkbox. The problem is: if I uncheck an item from the list box and click outside, popup disappears; if a I click again the checkbox is reseted at its initial value (all the items become checked).
So, how can I maintain the state of the popup set and stop its resetting while the app is running ? Can I do this through XAML ?
here is the code:
public class CheckedListItem<T> : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private bool isChecked = false;
private T item;
public CheckedListItem()
{ }
public CheckedListItem(T item, bool isChecked)
{
this.item = item;
this.isChecked = isChecked;
}
public T Item
{
get { return item; }
set
{
item = value;
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("Item"));
}
}
public bool IsChecked
{
get { return isChecked; }
set
{
isChecked = value;
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("IsChecked"));
}
}
}
the viewModel:
private void OnApplyFiltersCommandRaised(object obj)
{
if (FilterElement.Contains("ClassView"))
{
switch (FilterElement)
{
case "buttonClassViewClassFilter":
FilteredClassViewItems.Clear();
FilteredFieldViewItems.Clear();
foreach (var filterItem in FilterItems)
{
if (filterItem.IsChecked == true)
{
FilteredClassViewItems.Add(classViewItems.First(c => c.ClassName == filterItem.Item));
FilteredFieldViewItems.Add(fieldViewItems.First(c => c.ClassName == filterItem.Item));
}
}
break;
...
public ObservableCollection<CheckedListItem<string>> FilterItems
{
get
{
return filterItems;
}
set
{
filterItems = value;
SetPropertyChanged("FilterItems");
}
}
the XAML part:
<ListBox x:Name="listBoxPopupContent"
Height="250"
ItemsSource="{Binding FilterItems}"
BorderThickness="0"
ScrollViewer.VerticalScrollBarVisibility="Auto">
<ListBox.Resources>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="FontSize" Value="8" />
<Setter Property="IsSelected" Value="{Binding IsChecked, Mode=TwoWay}" />
</Style>
</ListBox.Resources>
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding IsChecked}"
Content="{Binding Item}"
Command="{Binding DataContext.ApplyFiltersCommand,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type ListBox}}}"
CommandParameter="{Binding IsChecked,
RelativeSource={RelativeSource Self},
Mode=OneWay}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Thanks in advance !
If you want to keep the state, you can just create a new view that will contain your listbox. Then your popup will be
<Popup>
<views:MyListBoxview>
</Popup>
where views is the path where wpf can find MyListBoxview.
This is an example of how you can do MyListBoxView. First of all, add a new usercontrol to your project. Then you create:
<ListBox ItemSource = {Binding MyCollectionOfItem}>
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox IsChecked = {Binding IsItemChecked} Content = {Binding Name}/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
You will need to assign to this view a viewmodel that will of course implement INotifyPropertyChanged and that will have these this class defined inside it (also this class will implement INotifyPropertyChanged)
public class MyItem : INotifyPropertyChanged
{
public void SetPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
private bool isItemChecked = false;
public bool IsItemChecked
{
get { return isItemChecked; }
set
{
isItemChecked = value;
SetPropertyChanged("IsItemChecked");
}
}
private string name ;
public string Name
{
get { return Name; }
set
{
name = value;
SetPropertyChanged("Name");
}
}
}
finally, the viewmodel that will represent the state of the popup will have inside this property
private ObservableCollection<MyItem> myCollectionOfItem = new ObservableCollection<MyItem>();
public ObservableCollection<MyItem> MyCollectionOfItem
{
get { return myCollectionOfItem; }
set
{
myCollectionOfItem = value;
SetPropertyChanged("MyCollectionOfItem");
}
}
I usually handle this kind of problem modelling properly the object that i need to bind to my controls in WPF

WPF TreeView not showing childs

I'm implementing a custom designed FolderSelector and I'm using a TreeView to show the folders.
This is the view:
<TreeView Grid.Row="1" Background="Transparent" ItemsSource="{Binding Path=Items}">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsSelected" Value="{Binding Path=IsSelected}" />
</Style>
</TreeView.ItemContainerStyle>
<TreeView.Resources>
<HierarchicalDataTemplate DataType="viewModel:FolderViewModel" ItemsSource="{Binding Path=Items}">
<DockPanel>
<fa:FontAwesome Icon="FolderOutline" Foreground="#dd5d18" VerticalAlignment="Center" />
<Label Content="{Binding}" VerticalAlignment="Center" FontWeight="SemiBold" />
</DockPanel>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
This is the viewmodel for the whole dialog
internal class FolderBrowserViewModel : BaseBAObject
{
#region Fields
#endregion
#region Properties
public ObservableCollection<FolderViewModel> Items { get; }
#endregion
#region Construction
public FolderBrowserViewModel()
{
Items = new ObservableCollection<FolderViewModel>();
LoadItems();
}
#endregion
#region Methods
private void LoadItems()
{
try
{
var drives = DriveInfo.GetDrives();
foreach (var drive in drives)
{
Items.Add(new FolderViewModel(drive.Name.Replace(#":\", ""), drive.Name));
}
}
catch (Exception)
{
// Ignore
}
}
#endregion
}
And this is the viewmodel for every folder
internal class FolderViewModel : BaseBAObject
{
#region Fields
private bool _isSelected;
private bool _isLoaded;
#endregion
#region Properties
public string Path { get; }
public string Name { get; }
public ObservableCollection<FolderViewModel> Items { get; }
public bool IsSelected
{
get { return _isSelected; }
set
{
if (value != _isSelected)
{
_isSelected = value;
OnPropertyChanged(nameof(IsSelected));
LoadSubfolders();
}
}
}
#endregion
#region Construction
public FolderViewModel(string name, string path)
{
Path = path;
Name = name;
Items = new ObservableCollection<FolderViewModel>();
}
#endregion
#region Methods
public void LoadSubfolders(bool force = false)
{
if(_isLoaded && !force)
return;
try
{
var dirs = Directory.GetDirectories(Path);
Items.Clear();
foreach (var dir in dirs)
{
Items.Add(new FolderViewModel(dir, System.IO.Path.Combine(Path, dir)));
}
_isLoaded = true;
}
catch (Exception)
{
// ignore
}
}
public override string ToString()
{
return Name;
}
#endregion
}
I'm now facing several problems
The data template is not applied to the TreeView root objects nor childs
The childs are not displayed (they are properly loaded when a root item is selected)
I want to show a unlimited count of childs
Be explicit about the type argument, i.e. use {x:Type}, and bind the Content property of the Label to the Name property of the FolderViewModel:
<HierarchicalDataTemplate DataType="{x:Type viewModel:FolderViewModel}" ItemsSource="{Binding Path=Items}">
<DockPanel>
<Label Content="{Binding Name}" VerticalAlignment="Center" FontWeight="SemiBold" />
</DockPanel>
</HierarchicalDataTemplate>

Binding in DataTrigger seems not working

I have a ComboBox which should have a text when nothing is selected. This seems like a straight forward problem, and has many answers on the net, but unfortunately, it is not working for me. I think, the reason is, that I don't want to show a static text, but rather a bound text.
My minimal not working example looks like this:
public class Model
{
public string Name { get; set; }
public SubModel SelectedItem { get; set; }
public List<SubModel> Items { get; set; }
}
public class SubModel
{
public string Description { get; set; }
}
and the MainWindow:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var selectedSubModel = new SubModel { Description = "SubModel5" };
var model1 = new Model
{
Name = "Model1",
Items = new List<SubModel>
{
new SubModel { Description = "SubModel1" },
new SubModel { Description = "SubModel2" },
new SubModel { Description = "SubModel3" }
}
};
var model2 = new Model
{
Name = "Model2",
SelectedItem = selectedSubModel,
Items = new List<SubModel>
{
new SubModel { Description = "SubModel4" },
selectedSubModel,
new SubModel { Description = "SubModel6" }
}
};
var model3 = new Model
{
Name = "Model3",
Items = new List<SubModel>
{
new SubModel { Description = "SubModel7" },
new SubModel { Description = "SubModel8" },
new SubModel { Description = "SubModel9" }
}
};
_itemsControl.Items.Add(model1);
_itemsControl.Items.Add(model2);
_itemsControl.Items.Add(model3);
}
}
with xaml:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:WpfApplication1="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<ItemsControl x:Name="_itemsControl">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="WpfApplication1:Model">
<ComboBox ItemsSource="{Binding Items}"
SelectedItem="{Binding SelectedItem}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Description}"></TextBlock>
</DataTemplate>
</ComboBox.ItemTemplate>
<ComboBox.Style>
<Style TargetType="ComboBox">
<Style.Triggers>
<DataTrigger Binding="{Binding SelectedItem}" Value="{x:Null}">
<Setter Property="Background">
<Setter.Value>
<VisualBrush>
<VisualBrush.Visual>
<TextBlock Text="{Binding Name}"/>
</VisualBrush.Visual>
</VisualBrush>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ComboBox.Style>
</ComboBox>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Window>
This gives the follwing:
But it should look similar to this:
please first of all take in your mind facts provided in the next sentence - you can only select items provided by ComboBox ItemsSource. Thus since the Name property values (Model1, Model2, Model3 etc.) are not in your collection they can't be selected you will see the empty selection Instead. I can suggest you the next solution the combination of data context proxy and wpf behavior.
Xaml code
<Window x:Class="ComboBoxWhenNoAnySelectedHelpAttempt.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:comboBoxWhenNoAnySelectedHelpAttempt="clr-namespace:ComboBoxWhenNoAnySelectedHelpAttempt"
xmlns:system="clr-namespace:System;assembly=mscorlib"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
Title="MainWindow" Height="350" Width="525">
<Grid>
<ItemsControl x:Name="_itemsControl">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="comboBoxWhenNoAnySelectedHelpAttempt:Model">
<ComboBox x:Name="ComboBox"
SelectedItem="{Binding SelectedItem, UpdateSourceTrigger=PropertyChanged}">
<ComboBox.Resources>
<!--the next object is a proxy that able to provide combo data context each time it requested-->
<comboBoxWhenNoAnySelectedHelpAttempt:FreezableProxyClass x:Key="FreezableProxyClass" ProxiedDataContext="{Binding ElementName=ComboBox, Path=DataContext }"></comboBoxWhenNoAnySelectedHelpAttempt:FreezableProxyClass>
</ComboBox.Resources>
<ComboBox.ItemsSource>
<CompositeCollection>
<!--the next object is a collapsed combo box that can be selected in code-->
<!--keep im mind, since this object is not a SubModel we get the binding expression in output window-->
<ComboBoxItem IsEnabled="False" Visibility="Collapsed" Foreground="Black" Content="{Binding Source={StaticResource FreezableProxyClass},
Path=ProxiedDataContext.Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"></ComboBoxItem>
<CollectionContainer Collection="{Binding Source={StaticResource FreezableProxyClass},
Path=ProxiedDataContext.Items, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</CompositeCollection>
</ComboBox.ItemsSource>
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Description}"></TextBlock>
</DataTemplate>
</ComboBox.ItemTemplate>
<i:Interaction.Behaviors>
<!--next behavior helps to select a zero index (Model.Name collapsed) item from source when selected item is not SubModel-->
<comboBoxWhenNoAnySelectedHelpAttempt:ComboBoxLoadingBehavior/>
</i:Interaction.Behaviors>
</ComboBox>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
Code Behind with Proxy Code
public class FreezableProxyClass : Freezable
{
protected override Freezable CreateInstanceCore()
{
return new FreezableProxyClass();
}
public static readonly DependencyProperty ProxiedDataContextProperty = DependencyProperty.Register(
"ProxiedDataContext", typeof(object), typeof(FreezableProxyClass), new PropertyMetadata(default(object)));
public object ProxiedDataContext
{
get { return (object)GetValue(ProxiedDataContextProperty); }
set { SetValue(ProxiedDataContextProperty, value); }
}
}
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var selectedSubModel = new SubModel { Description = "SubModel5" };
var model1 = new Model
{
Name = "Model1",
Items = new ObservableCollection<SubModel>
{
new SubModel { Description = "SubModel1" },
new SubModel { Description = "SubModel2" },
new SubModel { Description = "SubModel3" }
}
};
var model2 = new Model
{
Name = "Model2",
SelectedItem = selectedSubModel,
Items = new ObservableCollection<SubModel>
{
new SubModel { Description = "SubModel4" },
selectedSubModel,
new SubModel { Description = "SubModel6" }
}
};
var model3 = new Model
{
Name = "Model3",
Items = new ObservableCollection<SubModel>
{
new SubModel { Description = "SubModel7" },
new SubModel { Description = "SubModel8" },
new SubModel { Description = "SubModel9" }
}
};
_itemsControl.Items.Add(model1);
_itemsControl.Items.Add(model2);
_itemsControl.Items.Add(model3);
}
}
public class Model:BaseObservableObject
{
private string _name;
private SubModel _selectedItem;
private ObservableCollection<SubModel> _items;
public string Name
{
get { return _name; }
set
{
_name = value;
OnPropertyChanged();
}
}
public SubModel SelectedItem
{
get { return _selectedItem; }
set
{
_selectedItem = value;
OnPropertyChanged();
}
}
public ObservableCollection<SubModel> Items
{
get { return _items; }
set
{
_items = value;
OnPropertyChanged();
}
}
}
public class SubModel:BaseObservableObject
{
private string _description;
public string Description
{
get { return _description; }
set
{
_description = value;
OnPropertyChanged();
}
}
}
BaseObservableObject code (simple implementation for INotifyPropertyChanged )
/// <summary>
/// implements the INotifyPropertyChanged (.net 4.5)
/// </summary>
public class BaseObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
protected virtual void OnPropertyChanged<T>(Expression<Func<T>> raiser)
{
var propName = ((MemberExpression)raiser.Body).Member.Name;
OnPropertyChanged(propName);
}
protected bool Set<T>(ref T field, T value, [CallerMemberName] string name = null)
{
if (!EqualityComparer<T>.Default.Equals(field, value))
{
field = value;
OnPropertyChanged(name);
return true;
}
return false;
}
}
WPF Behavior Code
public class ComboBoxLoadingBehavior:Behavior<ComboBox>
{
private bool _unLoaded;
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.Loaded += AssociatedObjectOnLoaded;
AssociatedObject.LayoutUpdated += AssociatedObjectOnLayoutUpdated;
AssociatedObject.Unloaded += AssociatedObjectOnUnloaded;
}
private void AssociatedObjectOnUnloaded(object sender, RoutedEventArgs routedEventArgs)
{
_unLoaded = true;
UnsubscribeAll();
}
private void UnsubscribeAll()
{
AssociatedObject.Loaded -= AssociatedObjectOnLoaded;
AssociatedObject.LayoutUpdated -= AssociatedObjectOnLayoutUpdated;
AssociatedObject.Unloaded -= AssociatedObjectOnUnloaded;
}
private void AssociatedObjectOnLayoutUpdated(object sender, EventArgs eventArgs)
{
UpdateSelectionState(sender);
}
private static void UpdateSelectionState(object sender)
{
var combo = sender as ComboBox;
if (combo == null) return;
var selectedItem = combo.SelectedItem as SubModel;
if (selectedItem == null)
{
combo.SelectedIndex = 0;
}
}
private void AssociatedObjectOnLoaded(object sender, RoutedEventArgs routedEventArgs)
{
_unLoaded = false;
UpdateSelectionState(sender);
}
protected override void OnDetaching()
{
base.OnDetaching();
if(_unLoaded) return;
UnsubscribeAll();
}
}
This is a working complete solution for you problem, just copy/past and use it as a starting point for your farther research. I'll glad to help if you will have any problems with the code.
Regards.
I've found 2 possible solutions:
Change ComboBox template
Edit standard combobox template by right button click on combobox in designer and select Edit Template -> Edit a Copy...
After that change ContentPresenter with a custom converter:
XAML
<ContentPresenter ContentTemplate="{TemplateBinding SelectionBoxItemTemplate}" ContentTemplateSelector="{TemplateBinding ItemTemplateSelector}" ContentStringFormat="{TemplateBinding SelectionBoxItemStringFormat}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" IsHitTestVisible="false" Margin="{TemplateBinding Padding}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}">
<ContentPresenter.Content>
<MultiBinding Converter="{local:ComboboxEmptyValueConverter}">
<Binding Path="SelectionBoxItem" RelativeSource="{RelativeSource Mode=TemplatedParent}" />
<Binding Mode="OneWay" Path="DataContext" RelativeSource="{RelativeSource Mode=TemplatedParent}" />
</MultiBinding>
</ContentPresenter.Content>
</ContentPresenter>
C#
class ComboboxEmptyValueConverterExtension : MarkupExtension, IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
string stringValue = values[0] as string;
var dataContext = values[1] as Model;
return (stringValue != null && String.IsNullOrEmpty(stringValue)) ? dataContext?.Name : values[0];
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
return new object[] { value, null };
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
}
Set ComboBox to IsEditable & IsReadOnly and change Text
<ComboBox ItemsSource="{Binding Items}" SelectedItem="{Binding SelectedItem}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock x:Name="textBlock" Text="{Binding Description}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
<ComboBox.Style>
<Style TargetType="ComboBox">
<Style.Triggers>
<DataTrigger Binding="{Binding SelectedItem}" Value="{x:Null}">
<Setter Property="IsEditable" Value="True" />
<Setter Property="IsReadOnly" Value="True" />
<Setter Property="Text" Value="{Binding Name}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ComboBox.Style>
</ComboBox>
The answer is, to put the visual brush in the resources of the combobox:
<DataTemplate DataType="WpfApplication1:Model">
<ComboBox ItemsSource="{Binding Items}"
SelectedItem="{Binding SelectedItem}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Description}"></TextBlock>
</DataTemplate>
</ComboBox.ItemTemplate>
<ComboBox.Resources>
<VisualBrush x:Key="_myBrush">
<VisualBrush.Visual>
<TextBlock Text="{Binding Name}"/>
</VisualBrush.Visual>
</VisualBrush>
</ComboBox.Resources>
<ComboBox.Style>
<Style TargetType="ComboBox">
<Style.Triggers>
<DataTrigger Binding="{Binding SelectedItem}" Value="{x:Null}">
<Setter Property="Background" Value="{StaticResource _myBrush}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ComboBox.Style>
</ComboBox>
</DataTemplate>
Then, together with the rest of the code, it works like expected.

Access item in observable collection on item selected

I have a WPF TreeView populated by an observable collection using a hiarchialdatabinding
I need to access the item in my observable collection or the database that was used to populate it.
An example use case is that the user right clicks a treeview item to add a subgroup. I obviously need to access its parent to add the child.
Any suggestions? Im so lost..
I cant just edit the treeview item itself cause then the changes wont reflect back to my database
Database Code:
[Serializable]
public class LoginGroup
{
public string Name { get; set; }
public Guid ID { get; set; }
public List<Login> LoginItems = new List<Login>();
public List<LoginGroup> Children { get; set; }
}
public static ObservableCollection<LoginGroup> _GroupCollection = new ObservableCollection<LoginGroup>();
public ObservableCollection<LoginGroup> GroupCollection
{
get { return _GroupCollection; }
}
TreeView:
<TreeView x:Name="groupView" Width="211" TreeViewItem.Selected="OnTreeItemSelected" DockPanel.Dock="Left" Height="Auto" ItemsSource="{Binding GroupCollection}" >
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Path=Children}">
<TextBlock Text="{Binding Path=Name}" />
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
You can just cast the SelectedItem to LoginGroup:
LoginGroup selectedGroup = (LoginGroup)groupView.SelectedItem;
You can't reflect back changed of your properties because they don't have way to "notice" back that they are edited. You need inherit LoginGroup from DependencyObject or implement INotifyPropertyChanged
You should use TreeView's ItemContainer style.
Here's sample TreeNode view model:
public class TreeNode : ViewModel
{
public TreeNode()
{
this.children = new ObservableCollection<TreeNode>();
// the magic goes here
this.addChildCommand = new RelayCommand(obj => AddNewChild());
}
private void AddNewChild()
{
// create new child instance
var child = new TreeNode
{
Name = "This is a new child node.",
IsSelected = true // new child should be selected
};
// add it to collection
children.Add(child);
// expand this node, we want to look at the new child node
IsExpanded = true;
}
public String Name
{
get { return name; }
set
{
if (name != value)
{
name = value;
OnPropertyChanged("Name");
}
}
}
private String name;
public Boolean IsSelected
{
get { return isSelected; }
set
{
if (isSelected != value)
{
isSelected = value;
OnPropertyChanged("IsSelected");
}
}
}
private Boolean isSelected;
public Boolean IsExpanded
{
get { return isExpanded; }
set
{
if (isExpanded != value)
{
isExpanded = value;
OnPropertyChanged("IsExpanded");
}
}
}
private Boolean isExpanded;
public ObservableCollection<TreeNode> Children
{
get { return children; }
}
private ObservableCollection<TreeNode> children;
public ICommand AddChildCommand
{
get { return addChildCommand; }
}
private RelayCommand addChildCommand;
}
Some comments:
ViewModel is any base implementation of INotifyPropertyChanged
interface.
RelayCommand (a.k.a. DelegateCommand) is ICommand implementation for use in MVVM approach.
Here's the view:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<TreeView ItemsSource="{Binding}">
<TreeView.ItemContainerStyle>
<!-- Let's glue our view models with the view! -->
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu>
<!-- Here's menu item, which is responsible for adding new child node -->
<MenuItem Header="Add child..." Command="{Binding AddChildCommand}" />
</ContextMenu>
</Setter.Value>
</Setter>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Name}"/>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</Window>
... and sample data context initialization:
public MainWindow()
{
InitializeComponent();
DataContext = new ObservableCollection<TreeNode>
{
new TreeNode { Name = "Root", IsSelected = true }
};
}
Hope this helps.
Upd.
Of course, you have to expose child nodes as the ObservableCollection too. Otherwise, changes made to nodes collection won't be reflected.

Categories

Resources