WPF listbox groupping by substrings - wpf

My object has property that stores more strings separated by separator.
I want to display list of such objects in WPF listbox with grouping enabled. What I need is to have groups according to substrings.
Object1: Property = "string1;string2;string3"
Object2: Property = "string2;string3"
I expect listbox to be displayed like that:
string1
Object 1
Object 2
string2
Object 1
string3
Object 1
Object 2
Is that possible?
Thank you for your help.

Create a wrapper class.
class MyGroup
{
public string GroupID;
public object SomeObject;
}
Build your flat list of that wrapper class.
private List<MyGroup> BuildItemsSourceTogether()
{
List<MyGroup>itemsSource = new List<MyGroup>();
foreach(var obj in myListWithObjectsWhichPropertiesAreStringsWhichFuthermoreContainSubstrings)
{
var stringArray = obj.Property123.Split(';');
foreach(var str in stringArray)
{
itemsSource.Add(new MyGroup () { GroupID = str, SomeObject = obj});
}
}
return itemsSource;
}
Tell what property in your wrapper class should be used for grouping.
class Window1
{
public Window1()
{
InitalizeComponents();
var finalData = new ListCollectionView(BuildItemsSourceTogether());
finalData.GroupDescriptions.Add(new PropertyGroupDescription("GroupID"));
this.DataContext = finalData;
}
}
In XAML set the look of your groups and let WPF group that flat list of IDs for you.
I used a DataGrid with columns. You can use a ListBox.
<DataGrid ItemsSource="{Binding}">
<DataGrid.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Path=Name}" />
</StackPanel>
</DataTemplate>
</GroupStyle.HeaderTemplate>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander>
<Expander.Header>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Name}" />
<TextBlock Text="{Binding Path=ItemCount}"/>
<TextBlock Text="Items"/>
</StackPanel>
</Expander.Header>
<ItemsPresenter />
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</DataGrid.GroupStyle>
<DataGrid.Columns>
<DataGridTextColumn Header="header1" Binding="{Binding SomeObject.Property321}" />
<DataGridTextColumn Header="header2" Binding="{Binding SomeObject.Property678}" />
</DataGrid.Columns>
</DataGrid>
Have fun.

Related

Custom Control for a ComboBox with Default Element

What I want:
I'm trying to create a ComboBox which has a 'Default' element seperated from the rest of the items.
What I have:
So by following the countless threads about grouping the items and overwritting the ItemTemplate I came to this result:
.CS
namespace WpfApplication1
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new ObservableCollection<GroupItem>()
{
new GroupItem() { Name = "Default Item", GroupName = "Default"},
new GroupItem() { Name = "Item 1", GroupName = "Item"},
new GroupItem() { Name = "Item 2", GroupName = "Item"},
new GroupItem() { Name = "Item 3", GroupName = "Item"}
};
}
public class GroupItem
{
public string Name { get; set; }
public string GroupName { get; set; }
}
}
}
XAML
<Window.Resources>
<CollectionViewSource Source="{Binding}" x:Key="GroupedDataCollView">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="GroupName" />
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
<DataTemplate x:Key="GroupHeaderTemplate">
<TextBlock Text="{Binding GroupName}" Margin="10,0,0,0" Foreground="#989791" />
</DataTemplate>
<!--Here, we tell that the URL's description should be displayed as item text.-->
<DataTemplate x:Key="NameTemplate">
<TextBlock Text="{Binding Name}" Margin="10,0,0,0"/>
</DataTemplate>
<Style x:Key="GroupStyleContainerStyle" TargetType="{x:Type GroupItem}" >
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<StackPanel>
<Separator />
<ItemsPresenter />
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Window>
<!-- ... -->
<ComboBox Grid.Row="1" Grid.Column="1"
ItemsSource="{Binding Source={StaticResource GroupedDataCollView}}"
Width="200" Margin="10">
<ComboBox.GroupStyle>
<GroupStyle
ContainerStyle="{StaticResource GroupStyleContainerStyle}"
HeaderTemplate="{StaticResource GroupHeaderTemplate}" />
</ComboBox.GroupStyle>
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<!-- ... -->
</Window>
The Problem: Since we use Dictionaries for styling and templates which lie in another project, I can't use this 'solution' because this would require the style to know what kind of items he has to expect (Destroys the isolation of the custom styles and templates). Furthermore it's not really a group that I want to create rather a 'special' item, which is seperated by the others visually.
So I tried to create a custom UserControl to solve the problem so I could use it like this:
<ComboBox ItemsSource="{Binding Items}" DefaultItem="{Binding DefaultItem}" SelectedItem="{Binding SelectedItem}" />
In this CustomControl I would create the necessary grouping and handling hidden from the user. But how can I do this? And how can I overwrite the whole selection behaviour since I don't want to return a Key-Value-Pair (GroupItem) instead of the item the user expects.
So in short: How can I create a CustomControl which provides the necessary functionality shown at the start combined with the simplicity shown above?

How to make a recursive ListView in XAML

My model looks like
public class MyVm
{
public string MyTitle { get; set; }
public List<MyVm> Children { get; set; }
public MyVm()
{
this.Children = new List<MyVm>();
}
}
I want to be able to list through all the children and children's children, which I think is recursive.
The MainWindow code behind is
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
this.Kids = new List<MyVm>();
var m = new MyVm();
m.MyTitle = "Title1";
var m2 = new MyVm();
m2.MyTitle = "Title2";
var m3 = new MyVm();
m3.MyTitle = "Title3";
var m4 = new MyVm();
m4.MyTitle = "Title4";
m.Children.Add(m2);
m2.Children.Add(m3);
m3.Children.Add(m4);
this.Kids.Add(m);
}
public List<MyVm> Kids { get; set; }
and finally the MainWIndow view is
<Grid.Resources>
<Style x:Key="MyStyle" TargetType="ListViewItem">
<Setter Property="ContentTemplate">
<Setter.Value>
<HierarchicalDataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding MyTitle}" />
<ListView ItemsSource="{Binding Children}" ItemContainerStyle="{Binding MyStyle}" />
</StackPanel>
</HierarchicalDataTemplate>
</Setter.Value>
</Setter>
</Style>
</Grid.Resources>
<ListView ItemsSource="{Binding Kids}" ItemContainerStyle="{StaticResource MyStyle}" />
As you can see I've tried to re-use the same resource for each 'children' to achieve the recursive bit, but sadly, the only thing I see rendered is a single TextBlock with Title2
For these purposes you would use a HierarchicalDataTemplate (which has its own ItemsSource property that you would bind to Children), i am not sure if a ListBox supports it. If not use a TreeView and change the control templates to remove the indentation and the collapse toggle button if you don't want that.
Fixed it
<Grid.Resources>
<DataTemplate DataType="{x:Type a:MyVm}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding MyTitle}" />
<ListView ItemsSource="{Binding Children}">
<ListView.ItemTemplate>
<DataTemplate>
<ContentControl Content="{Binding }" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
</DataTemplate>
</Grid.Resources>
<ListView ItemsSource="{Binding Kids}" />

DataGrid DataGridTemplateColumn ComboBox

I'm having trouble with my ComboBoxes in a DataGrid, let me explain with a few pictures.
This is the starting point.
Now if I want to add a new row I click the last row and hit Enter.
A new row is added and iv selected type table here and given it a name MY_TABLE, then I hit enter to add another row.
The result is this, the combobox for the previous added row's type selection has gone back to None. NOTE: that the checkboxes were previously grayed out as type None cant have any privileges, but table can have CRUD so when I selected type table they became enabled.
Here is the ViewModel (VM) for each row:
public class RoleHasPrivilegeOnObjectEntityViewModel : EntityViewModelBase<RoleHasPrivilegeOnObjectEntityViewModel, RoleHasPrivilegesOnObject>, IRoleHasPrivilegeOnObjectListItemViewModel
{
private readonly RoleHasPrivilegesOnObject _roleHasPrivilegesOnObject;
private ObservableCollection<ObjectTypeEntityViewModel> _availableObjectTypes;
private readonly ObjectTypeEntityViewModel _objectTypeEntityViewModel;
private IRoleEntityViewModel _role;
private IObjectEntityViewModel _object;
public RoleHasPrivilegeOnObjectEntityViewModel(RoleHasPrivilegesOnObject roleHasPrivilegesOnObject, IEnumerable<OBJECT_TYPE> availableObjectTypes)
{
_roleHasPrivilegesOnObject = roleHasPrivilegesOnObject;
AvailableObjectTypes = new ObservableCollection<ObjectTypeEntityViewModel>(availableObjectTypes.Select(ot => new ObjectTypeEntityViewModel(ot)));
_role = new RoleEntityViewModel(_roleHasPrivilegesOnObject.Role);
_object = new ObjectEntityViewModel(_roleHasPrivilegesOnObject.Object);
_objectTypeEntityViewModel = new ObjectTypeEntityViewModel(_roleHasPrivilegesOnObject.Object.OBJECT_TYPE);
}
public RoleHasPrivilegeOnObjectEntityViewModel(XROLE role, CONTAINER schema, OBJECT_TYPE currentObjectType, IEnumerable<OBJECT_TYPE> availableObjectTypes)
{
var objectTypes = availableObjectTypes as IList<OBJECT_TYPE> ?? availableObjectTypes.ToList();
_roleHasPrivilegesOnObject = new RoleHasPrivilegesOnObject(role,
new XOBJECT { CONTAINER = schema, OBJECT_TYPE = currentObjectType },
new List<OBJECT_HAS_PRIVILEGE>(),
objectTypes.SelectMany(aot => aot.PRIVILEGE));
AvailableObjectTypes = new ObservableCollection<ObjectTypeEntityViewModel>(objectTypes.Select(ot => new ObjectTypeEntityViewModel(ot)));
_role = new RoleEntityViewModel(_roleHasPrivilegesOnObject.Role);
_object = new ObjectEntityViewModel(_roleHasPrivilegesOnObject.Object);
_objectTypeEntityViewModel = new ObjectTypeEntityViewModel(_roleHasPrivilegesOnObject.Object.OBJECT_TYPE);
}
public override EntityType EntityType
{
get { return SelectedObjectType.EntityType; }
}
public ObjectTypeEntityViewModel SelectedObjectType
{
get { return _objectTypeEntityViewModel; }
set
{
_roleHasPrivilegesOnObject.Object.OBJECT_TYPE = value.OriginalEntity;
OnPropertyChanged();
OnPropertyChanged("CanHaveSelect");
...
}
}
public ObservableCollection<ObjectTypeEntityViewModel> AvailableObjectTypes
{
get { return _availableObjectTypes; }
set
{
_availableObjectTypes = value;
OnPropertyChanged();
}
}
public string ToolTip
{
get { return _roleHasPrivilegesOnObject.ToolTip; }
}
public bool HasSelect
{
get { return _roleHasPrivilegesOnObject.HasSelect; }
set
{
_roleHasPrivilegesOnObject.HasSelect = value;
OnPropertyChanged();
}
}
public bool CanHaveSelect
{
get
{
var canHaveSelect = _roleHasPrivilegesOnObject.CanHaveSelect;
if(!canHaveSelect && HasSelect) HasSelect = false;
return canHaveSelect;
}
}
...
public override string NAME
{
get { return _roleHasPrivilegesOnObject.NAME; }
set
{
_roleHasPrivilegesOnObject.NAME = value;
OnPropertyChanged();
OnPropertyChanged("Text");
}
}
}
And here is my View for the DataGrid
<UserControl ...
d:DataContext="{d:DesignInstance impl:PrivilegeDetailsViewModel}">
<UserControl.Resources>
<Style x:Key="DataGridContentCellCentering" TargetType="{x:Type DataGridCell}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type DataGridCell}">
<Grid Background="{TemplateBinding Background}">
<ContentPresenter VerticalAlignment="Center" HorizontalAlignment="Center"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="CanHaveSelectStyle" TargetType="CheckBox">
<Style.Triggers>
<DataTrigger Binding="{Binding CanHaveSelect, UpdateSourceTrigger=PropertyChanged}" Value="True">
<Setter Property="IsEnabled" Value="True"/>
</DataTrigger>
<DataTrigger Binding="{Binding CanHaveSelect, UpdateSourceTrigger=PropertyChanged}" Value="False">
<Setter Property="IsEnabled" Value="False"/>
</DataTrigger>
</Style.Triggers>
</Style>
...
</UserControl.Resources>
<DataGrid x:Name="PrivilegeDataGrid"
ItemsSource="{Binding RolesHasPrivilegesOnObjects}"
AutoGenerateColumns="False"
CanUserReorderColumns="False"
CanUserResizeColumns="True"
CanUserResizeRows="False"
CanUserSortColumns="True"
CanUserAddRows="True"
IsTextSearchEnabled="True"
BorderThickness="0">
<DataGrid.Columns>
<DataGridTemplateColumn Header="Type" CanUserSort="True" MinWidth="120">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate DataType="{x:Type impl2:RoleHasPrivilegeOnObjectEntityViewModel}">
<ComboBox ItemsSource="{Binding AvailableObjectTypes}"
SelectedItem="{Binding SelectedObjectType}"
SelectedValue="{Binding SelectedObjectType.ID}"
SelectedValuePath="ID">
<ComboBox.ItemTemplate>
<DataTemplate DataType="{x:Type impl2:ObjectTypeEntityViewModel}">
<StackPanel Orientation="Horizontal">
<Image Source="{Binding Icon}" ToolTip="{Binding ToolTip}" Margin="0,0,3,0" Width="17" Height="17"/>
<TextBlock Text="{Binding ToolTip}"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Width="*" Header="Name" Binding="{Binding NAME}">
<DataGridTextColumn.EditingElementStyle>
<Style TargetType="TextBox">
<Setter Property="extensions:TextBoxUpperCaseBehavior.IsEnabled" Value="True"/>
</Style>
</DataGridTextColumn.EditingElementStyle>
</DataGridTextColumn>
<DataGridCheckBoxColumn Header="Select"
Binding="{Binding HasSelect, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
CellStyle="{StaticResource DataGridContentCellCentering}"
ElementStyle="{StaticResource CanHaveSelectStyle}"
EditingElementStyle="{StaticResource CanHaveSelectStyle}" />
...
</DataGrid.Columns>
</DataGrid>
</UserControl>
I have tried all these variants for the ComboBox ItemsSource and SelectedItem
<ComboBox ItemsSource="{Binding AvailableObjectTypes}"
SelectedItem="{Binding SelectedObjectType}"
SelectedValue="{Binding SelectedObjectType.ID}"
SelectedValuePath="ID">
<ComboBox ItemsSource="{Binding AvailableObjectTypes}"
SelectedValue="{Binding SelectedObjectType.ID}"
SelectedValuePath="ID">
<ComboBox ItemsSource="{Binding AvailableObjectTypes}"
SelectedItem="{Binding SelectedObjectType}"
SelectedValuePath="ID">
<ComboBox ItemsSource="{Binding AvailableObjectTypes}"
SelectedItem="{Binding SelectedObjectType}">
What do I have to do to make the ComboBox behave as expected?
It seems that a simple UpdateSourceTrigger attribute was missing on the SelectedObjectType binding in the xaml for the ComboBox, like this
<ComboBox ItemsSource="{Binding AvailableObjectTypes}"
SelectedItem="{Binding SelectedObjectType, UpdateSourceTrigger=PropertyChanged}"
SelectedValue="{Binding SelectedObjectType.ID}"
SelectedValuePath="ID">

wpf datagrid automatically expand first group

I have a datagrid with the itemsource bound to a ListCollectionView with one group.
When i fill the collection, i want the first group autmatically viewed as expanded, how to code that in wpf (codebehind or mvvm)?
<DataGrid
ItemsSource="{Binding ResultColl}"
SelectedItem="{Binding Path=SelectedResultItem, Mode=TwoWay}"
SelectionMode="Single" IsReadOnly="True" >
<DataGrid.GroupStyle>
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander>
<Expander.Header>
<StackPanel>
<TextBox Text="{Binding Items[0].ID}" />
</StackPanel>
</Expander.Header>
<ItemsPresenter />
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</DataGrid.GroupStyle>
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Path=ID}"/>
<DataGridTextColumn Binding="{Binding Path=Typ}"/>
<DataGridTextColumn Binding="{Binding Path=Info}"/>
<DataGridTextColumn Binding="{Binding Path=orderDate, StringFormat={}{0:dd-MM-yyyy}}"/>
</DataGrid.Columns>
</DataGrid>
In the mvvm controller:
ListCollectionView tmp = new ListCollectionView(myList);
tmp.GroupDescriptions.Add(new PropertyGroupDescription("ID"));
ResultColl = tmp;
...
ListCollectionView _resultColl;
public ListCollectionView ResultColl
{
get { return _resultColl; }
set { _resultColl = value;
RaisePropertyChanged("ResultColl");
if (value != null && _resultColl.Count > 0)
SelectedResultItem = _resultColl.GetItemAt(0) as ItemResult;
}
}
When executing the code, the datagrid is filled the 1st item is selected but group is collapsed.
Add IsExpanded property to your class and add binding to Expander:
<Expander IsExpanded="{Binding Items[0].IsExpanded}">
Set IsExpanded for first to true
you can try add another bool property to your View Model defaulted to true but switching to false when first time used. And bind IsExpanded property of Expander to this with OneTime mode.
public bool IsExpanded
{
get
{
if (_isExpanded)
{
_isExpanded = false;
return true;
}
return false;
}
}
Xaml would be like that:
<Expander IsExpanded="{Binding DataContext.IsExpanded, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}, Mode=OneTime}">

WPF: display a collection of datagrids in a custom format (with image)

I have 2 classes, one inside the other, and a prop with an ObservableCollection of the class with the sub-class collection. But I'm having a serious trouble in displaying the whole thing.
First my data, this is what I've got: (it may clarify my issue)
public class MyItem
{
public string Id { get; set; }
public string Front { get; set; }
public Props.StateSemaphore Semaphore{ get; set; } // this is an enum w/ints
public string ToolTip { get; set; }
public string Architect { get; set; }
public string Status { get; set; }
public MyItem(){}
public MyItem(string id, string front,
Props.StateSemaphore semaphore, string toolTip,
string architect, string status)
{
Id = id;
Front = frente;
Semaphore = semaphore;
ToolTip = toolTip;
Architect = architect;
Status = status;
}
}
public class MyTab
{
public List<MyItem> MyItems { get; set; }
public string Environment { get; set; }
public MyTab() { }
public MyTab(string environment)
{
Environment = environment;
MyItems = new List<MyItem>();
}
}
And a prop on the PageExample.xaml.cs
private ObservableCollection<MyTab> myPanel;
public ObservableCollection<MyTab> MyPanel
{
get { return myPanel; }
set { myPanel = value; }
}
The idea is to display for each Environment a Grid of MyItems with an image(Red, Yellow or Green) on the semaphore enum
#Edit: This is almost working! Only the images won't display.
This is My XAML but im newbie on wpf so It's obvious I’m missing something.
<Page x:Class="MyBoard.PageMain"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:w="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:MyBoard"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
Title="PageMain">
<Grid x:Name="LayoutRoot" Background="White" HorizontalAlignment="Center">
<DataGrid Name="EnvironmentDataGrid" IsReadOnly="True" ItemsSource="{Binding}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Id}" Header="Id"/>
<DataGridTextColumn Binding="{Binding Front}" Header="Front"/>
<DataGridTemplateColumn Header="Semaphore">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Image Source="{Binding Semaphore}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Binding="{Binding ToolTip}" Header="ToolTip"/>
<DataGridTextColumn Binding="{Binding Architect}" Header="Architect"/>
<DataGridTextColumn Binding="{Binding Status}" Header="Status"/>
</DataGrid.Columns>
<DataGrid.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Path=Environment}" FontWeight="Bold" Padding="3"/>
</StackPanel>
</DataTemplate>
</GroupStyle.HeaderTemplate>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander>
<Expander.Header>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Name}" />
<TextBlock Text="{Binding Path=ItemCount}" Margin="8,0,4,0"/>
<TextBlock Text="Element(s)"/>
</StackPanel>
</Expander.Header>
<ItemsPresenter />
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</DataGrid.GroupStyle>
</DataGrid>
</Grid>
</Page>
These are my questions:
How is the correct way to write the XAML? #Edit: DONE!
How and where to bind
the semaphore image to the datagrid?
#Edit: Semaphore is now a RelativeUri, because I didnt understand this answer.
I mean, I get the idea but not this thing:
<MultiBinding Converter={StaticResource catMultiConverter}>
<Binding .../>
<Binding .../>
</MultiBinding>
With the RelativeUri and all It still does not display.
See here to find out how to set DataGrid.Columns and how to Bind them.
Check here how to convert semaphore enums into Images thru Converter and DataGridTemplateColumn.CellTemplate.
See here how to use grouping in DataGrid to group on the Environment property so that same Environment items are shown arranged under one group.

Resources