I have a list box that is bound to hierarchial collection. The data has list menu items. There are two types of menus. First one is grouping menu, which will have child items. Second one is menu items which can be selected and this one does not have any children.
I have a property names HasChildren to distinguish between different type of menu. I want to apply different type of template for both types of menu. I want to show buttons for selectable(second) menus and tree view for menu with chidlren.
<ListBox ItemsSource="{Binding UserMenus}">
<ListBox.ItemTemplate>
<DataTemplate>
<TreeViewItem ItemsSource="{Binding Children}" Header="{Binding Name}">
<TreeViewItem.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<StackPanel>
<Button Width="250" Content="{Binding Name}" ToolTip="{Binding Name}" Style="{StaticResource MaterialDesignRaisedButton}"
Command="{Binding DataContext.TemplateCommand, RelativeSource={RelativeSource AncestorType=ListBox}}" CommandParameter="{Binding Id}"/>
</StackPanel>
</HierarchicalDataTemplate>
</TreeViewItem.ItemTemplate>
</TreeViewItem>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I tried this approach but it is not working for top level menu items, it is displaying tree view item for both types of menus.
My hierarchial data class.
public class MenuModel : ObservableObject, IMenuModel
{
string _name, _description, _id;
public MenuModel()
{
Children = new ObservableCollection<IMenuModel>();
}
public string Name { get => _name; set => SetProperty(ref _name, value); }
public string Description { get => _description; set => SetProperty(ref _description, value); }
public string Id { get => _id; set => SetProperty(ref _id, value); }
public bool HasChildren
{
get
{
bool returnValue = false;
if (Children != null && Children.Count > 0)
{
returnValue = true;
}
return returnValue;
}
}
public ObservableCollection<IMenuModel> Children { get; set; }
}
Help me to apply template properly to achieve desired result.
You can try this:
Xaml
<ListBox ItemsSource="{Binding UserMenus}">
<ListBox.Resources>
<HierarchicalDataTemplate x:Key="DataTemplate2" ItemsSource="{Binding Children}">
<StackPanel>
<Button Width="250" Content="{Binding Name}" ToolTip="{Binding Name}" Style="{StaticResource MaterialDesignRaisedButton}"
Command="{Binding DataContext.TemplateCommand, RelativeSource={RelativeSource AncestorType=ListBox}}" CommandParameter="{Binding Id}"/>
</StackPanel>
</HierarchicalDataTemplate>
<DataTemplate x:Key="DataTemplate1">
<TreeViewItem ItemsSource="{Binding Children}" Header="{Binding Name}"/>
</DataTemplate>
</ListBox.Resources>
</ListBox>
DataTemplateSelector
public sealed class MenuModelSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
if (item is MenuModel menuModel)
{
var resourceKey = menuModel.HasChildren ? "DataTemplate2" : "DataTemplate1";
if (container is FrameworkElement element && element.FindResource(resourceKey) is DataTemplate dataTemplate)
{
return dataTemplate;
}
}
return base.SelectTemplate(item, container);
}
}
Related
I write my own checkbox control. This checkbox, I put inside listbox using MVVM pattern. This user control have its own class, view model and xaml view.
Here is a class:
public class MultiSelectListBox
{
public bool IsChecked { get; set; }
public string Text { get; set; }
}
ViewModel for UserControl:
public partial class VMMultiSelectListBox : ViewModelBase
{
private bool _isChecked;
private string _text;
public VMMultiSelectListBox()
{
}
public VMMultiSelectListBox(MultiSelectListBox.BusinnesModel.MultiSelectListBox item)
{
IsChecked = item.IsChecked;
Text = item.Text;
}
public bool IsChecked
{
get { return _isChecked; }
set { _isChecked = value; NotifyPropertyChanged("IsChecked"); }
}
public string Text
{
get { return _text; }
set { _text = value; NotifyPropertyChanged("Text"); }
}
}
And here is xaml:
<UserControl x:Class="MES.UserControls.MultiSelectListBox.UCMultiSelectListBox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:MES.UserControls.MultiSelectListBox">
<CheckBox IsChecked="{Binding IsChecked, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Content="{Binding Text, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" />
</UserControl>
Now I want to bind this UserControl inside my ListBox, which is located in main form.
This is what I'm using in my form xaml.
<Expander x:Name="expanderProccesses" Header="Procesy" IsExpanded="{Binding IsExpanded}" Grid.Column="1" Grid.Row="0" VerticalAlignment="Top" Margin="5,6,-30,0">
<ListBox ScrollViewer.VerticalScrollBarVisibility="Disabled" ItemsSource="{Binding ProccessFilter}" SelectedItem="{Binding SelectedProcess, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<ucLb:UCMultiSelectListBox/>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
</Expander>
Last thing is view model of this form.
public VMMultiSelectListBox SelectedProcess
{
get { return _selectedProccess; }
set {
_selectedProccess = value;
NotifyPropertyChanged("SelectedProcess");
NotifyPropertyChanged("ProccessFilter");
}
}
public ObservableCollection<VMMultiSelectListBox> ProccessFilter
{
get { return _proccesFilter; }
set { _proccesFilter = value; NotifyPropertyChanged("ProccessFilter");}
}
Something I'm doing wrong. In selectedProcces it always leap in getter, but not in setter, which I need. I don't exactly know why.
I thing what you are trying to do can be achieved in a more standard context, by binding IsSelected property in ItemContainerStyle and using a CheckBox in the ItemTemplate:
<ListBox ScrollViewer.VerticalScrollBarVisibility="Disabled" SelectionMode="Extended" ItemsSource="{Binding ProccessFilter}">
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding IsChecked, Mode=TwoWay}" Content="{Binding Text}"/>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="IsSelected" Value="{Binding IsChecked, Mode=TwoWay}"/>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
Please note that you should set SelectionMode="Extended".
Hope it helps.
I've got a question, I have a list of checkbox in combobox box and it looks like this:
<StackPanel Orientation="Vertical" DataContext="{Binding CandidateEntity}">
<StackPanel Orientation="Horizontal">
<ComboBox ItemsSource="{Binding DataContext.SkillSetEntities, ElementName=CrudCandidate }"
IsEditable="True" IsReadOnly="True" Text="Umiejętności">
<ComboBox.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding Name}" IsChecked="{Binding IsChecked}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</StackPanel>
</StackPanel>
Now I also have a collection of skillset objects in binded item source (CandidateEntity.SkillSets), now how can I check those checkboxes that are in my collection of skillset objects?
I want to create a edition for CandidateEntity object in form and part of that edition is list of skillset that is represented in combobox.
EDIT:
I have solved problem by adding to skillset model prop:
private bool _isSelected = false;
[NotMapped]
public bool IsSelected
{
get
{
return this._isSelected;
}
set
{
_isSelected = value;
}
}
And then in view model:
private List<SkillSet> GetSkillSets()
{
var skillsetList = this._catalog.SkillSets.ToList();
var candidateSkillsetList = this.CandidateEntity.SkillSets.ToList();
foreach (SkillSet skillset in skillsetList)
{
foreach (SkillSet candidateSkillset in candidateSkillsetList)
{
if (skillset.id == candidateSkillset.id)
{
skillset.IsSelected = true;
}
}
}
return skillsetList;
}
and in checkbox in wpf:
<CheckBox Content="{Binding Name}" IsChecked="{Binding IsSelected}"/>
BUT I am pretty sure there must be easier way to handle that, is there?
I have a simple dialog with MVVM, just a ListView and two Buttons (Ok, Cancel) and a ViewModel responsible for binding the ItemsSource and SelectedItem of the ListView.
View:
...
<StackPanel>
<ScrollViewer VerticalScrollBarVisibility="Auto">
<ListView ItemsSource="{Binding Projects}" SelectedItem="{Binding Project}" SelectionMode="Single">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ScrollViewer>
<StackPanel Orientation="Horizontal" FlowDirection="RightToLeft">
<Button Content="Cancel" IsCancel="True"/>
<Button Content="Ok" Command="{Binding OkCommand}" CommandParameter="{Binding ElementName=Dlg}"/>
</StackPanel>
</StackPanel>
...
Viewmodel:
public class SelectProjectViewModel : ViewModelBase, IModalDialogViewModel
{
private readonly ProjectList _projectList;
public ProjectList Projects => _projectList;
public Project Project
{
get { return GetValue(() => Project); }
set { SetValue(() => Project, value);}
}
private RelayCommand _okCommand;
public RelayCommand OkCommand => _okCommand ?? (_okCommand = new RelayCommand(OkCommandCall));
public SelectProjectViewModel(ProjectList projectList, Project currentProject)
{
_projectList = projectList;
this.Project = currentProject;
}
private void OkCommandCall(object window)
{
DialogResult = true;
WindowCloseBehaviour.SetClose(window as DependencyObject, true);
}
public bool? DialogResult
{
get { return GetValue(() => DialogResult); }
private set { SetValue(() => DialogResult, value); }
}
}
I tried to achieve focusing the first Element like described here, with the accepted solution and the equivalent behaviour class. But this always sets the focus on the Cancel-Button, while I want my ListView to be focused when opening the dialog, so that the selected element is really highlighted, not just in the "inactive highlight" color. And no, I don't want to workaround with changing the colors.
Using simple FocusedElement on a parent with SelectedIndex on ListView itself works for me:
<StackPanel FocusManager.FocusedElement="{Binding ElementName=myListView}">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<ListView Name="myListView" ItemsSource="{Binding Projects}" SelectedItem="{Binding Project}" SelectionMode="Single"
SelectedIndex="0">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ScrollViewer>
...
</StackPanel>
</StackPanel>
I have a grid bound to a collection of VMs. When using DataTemplateSelector for my DataGridTemplateColumn I'm getting the whole VM as a data item, how do I narrow it down to a specific property value (otherwise I have to create 'DataTemplateSelector' for each VM or use interfaces, with both are too cumbersome) ?
Saw Bind a property to DataTemplateSelector, but it looks like a nasty workaround.
You can use Expressions Trees in a DataTemplateSelector's derived class, that I called PropertyTemplateSelector. Here its code:
public abstract class PropertyTemplateSelector : DataTemplateSelector
{
private Delegate getPropertyValue;
private string propertyName;
private Type itemType;
public string PropertyName
{
get
{
return propertyName;
}
set
{
propertyName = value;
}
}
public Type ItemType
{
get
{
return itemType;
}
set
{
itemType = value;
}
}
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
if (ItemType.IsInstanceOfType(item))
{
if (getPropertyValue == null)
{
System.Linq.Expressions.ParameterExpression instanceParameter =
System.Linq.Expressions.Expression.Parameter(item.GetType(), "p");
System.Linq.Expressions.MemberExpression currentExpression =
System.Linq.Expressions.Expression.PropertyOrField(instanceParameter, PropertyName);
System.Linq.Expressions.LambdaExpression lambdaExpression =
System.Linq.Expressions.Expression.Lambda(currentExpression, instanceParameter);
getPropertyValue = lambdaExpression.Compile();
}
return SelectTemplateImpl(getPropertyValue.DynamicInvoke(item), container);
}
return base.SelectTemplate(item, container);
}
protected abstract DataTemplate SelectTemplateImpl(object propertyValue, DependencyObject container);
}
You can extend this class with your own logic, just by implementing the SelectTemplateImpl method. As you can see the PropertyTemplateSelector narrows the item object down to a specific property value (which is passed to the SelectTemplateImpl method). For example I created a NameTemplateSelector in this way:
public class NameTemplateSelector : PropertyTemplateSelector
{
protected override DataTemplate SelectTemplateImpl(object propertyValue, DependencyObject container)
{
string name = (string)propertyValue;
if (name != null && name.StartsWith("A", StringComparison.OrdinalIgnoreCase))
{
return (DataTemplate)App.Current.MainWindow.FindResource("VipName");
}
return (DataTemplate)App.Current.MainWindow.FindResource("NormalName");
}
}
The you can easly use these template selectors in your XAML
<Window.Resources>
<DataTemplate x:Key="NormalName">
<TextBlock Text="{Binding Mode=OneWay, Path=Name}" Margin="3" />
</DataTemplate>
<DataTemplate x:Key="VipName">
<TextBlock Text="{Binding Mode=OneWay, Path=Name}" Margin="3" Foreground="Red" FontWeight="Bold" />
</DataTemplate>
<DataTemplate x:Key="NormalSurname">
<TextBlock Text="{Binding Mode=OneWay, Path=Surname}" Margin="3" />
</DataTemplate>
<DataTemplate x:Key="VipSurname">
<TextBlock Text="{Binding Mode=OneWay, Path=Surname}" Margin="3" Foreground="Green" FontStyle="Italic" />
</DataTemplate>
<local:NameTemplateSelector x:Key="NameTemplateSelector" PropertyName="Name" ItemType="{x:Type local:Person}" />
<local:SurnameTemplateSelector x:Key="SurnameTemplateSelector" PropertyName="Surname" ItemType="{x:Type local:Person}" />
</Window.Resources>
<Grid>
<DataGrid ItemsSource="{Binding Path=People, Mode=OneWay}" AutoGenerateColumns="False"
CanUserAddRows="False">
<DataGrid.Columns>
<DataGridTemplateColumn Header="Name"
CellTemplateSelector="{StaticResource NameTemplateSelector}" />
<DataGridTemplateColumn Header="Surname"
CellTemplateSelector="{StaticResource SurnameTemplateSelector}" />
</DataGrid.Columns>
</DataGrid>
</Grid>
I hope it can help you.
I am trying to create a TreeView with the following hierarchy:
Device1
--File1
--File2
--Hook1
--Hook2
Device2
--File1
--File1
Device3
--Hook1
So basically The Root level node is a device with children being File and Hook. I have created the following tree with the hierarchical data template
<TreeView ItemsSource="{Binding Devices}">
<HierarchicalDataTemplate DataType="{x:Type datamodel:Device}" ItemsSource="{Binding Files}">
<TextBlock Margin="5,5,0,0" Text="{Binding DeviceName}"/>
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate DataType="{x:Type datamodel:File}">
<TextBlock Margin="5,0,0,0" Text="{Binding FileName}"/>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
Class Devices
{
public string DeviceName
{
get;set;
}
public string List<File> Files
{
get;set;
}
public string List<Hook> Hooks
{
get;set;
}
}
public class File
{
public string FileName
{
get;set;
}
public string FileType
{
get;set;
}
public string Location
{
get; set;
}
}
public class Hook
{
public string HookName
{
get;set;
}
public string Type
{
get;set;
}
}
I am able to add only one Datatemplate in the ItemTemplate of the HierarchicalDataTemplate. How do I specify two data types under a single HierarchicalDataTemplate??
You will have to use the converter here which will return the CompositeCollection which will contain both files and hooks. You will use this converter with ItemsSource of HierarchicalDataTemplate DataType="{x:Type datamodel:Device}"
Then you just need to define two datatemplates for File and Hook data type.
Thanks
I tried with the CompositeCollection but realized that the bound objects were converted to base types. So I used it in combination of DataTemplateSelector.
The following solution worked for me.
<TreeView ItemsSource="{Binding Devices}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type datamodel:Devices}" ItemTemplateSelector="{StaticResource LeafDataTemplateSelector}">
<HierarchicalDataTemplate.ItemsSource>
<MultiBinding Converter="{StaticResource CompositeCollectionConverter}">
<Binding Path="Files" />
<Binding Path="Hooks"/>
</MultiBinding>
</HierarchicalDataTemplate.ItemsSource>
<StackPanel Height="25" Orientation="Horizontal">
<Image Height="20" Width="20" Source="Device.png"/>
<TextBlock Margin="5,5,0,0" Text="{Binding Device}"/>
</StackPanel>
</HierarchicalDataTemplate>
<DataTemplate x:Key="FileKey" DataType="{x:Type datamodel:File}">
<StackPanel Height="25" Orientation="Horizontal" ToolTip="Installation File">
<Image Height="20" Width="20" Source="File.png" />
<TextBlock Text="{Binding FileName}"/>
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="HookKey" DataType="{x:Type datamodel:Hook}">
<StackPanel Height="25" Orientation="Horizontal" ToolTip="Installation Hook">
<Image Height="20" Width="20" Source="Hook.png" />
<TextBlock Margin="5,5,0,0" Text="{Binding HookName}"/>
</StackPanel>
</DataTemplate>
</TreeView.Resources>
The have used the template selector to select the appropriate template based on the key.
public class LeafDataTemplateSelector : DataTemplateSelector
{
public override DataTemplate
SelectTemplate(object item, DependencyObject container)
{
FrameworkElement element = container as FrameworkElement;
if (element != null && item != null)
{
if (item is InstallationManifesFile)
return
element.FindResource("FileKey")
as DataTemplate;
else if (item is InstallationManifestHook)
return element.FindResource("HookKey")
as DataTemplate;
}
return null;
}
}
public class CompositeCollectionConverter : IMultiValueConverter
{
public object Convert(object[] values
, Type targetType
, object parameter
, System.Globalization.CultureInfo culture)
{
var res = new CompositeCollection();
foreach (var item in values)
if (item is IEnumerable && item != null)
res.Add(new CollectionContainer()
{
Collection = item as IEnumerable
});
return res;
}
public object[] ConvertBack(object value
, Type[] targetTypes
, object parameter
, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}