How to access to controls inside a GroupItem? - c#

I'm using a CollectionView to group my item as following:
<CollectionViewSource Source="{Binding Competitions}" x:Key="GroupedData">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="Item.Country" />
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
the full XAML structure is this:
<ComboBox x:Name="CompetitionCombo"
ItemsSource="{Binding Source={StaticResource GroupedData}}"
ItemTemplate="{StaticResource CombinedTemplate}">
<ComboBox.GroupStyle>
<GroupStyle ContainerStyle="{StaticResource containerStyle}"
HeaderTemplate="{StaticResource GroupHeader}">
</GroupStyle>
</ComboBox.GroupStyle>
<DataTemplate x:Key="CombinedTemplate">
<ContentPresenter x:Name="Presenter"
Content="{Binding}"
ContentTemplate="{StaticResource NormalItemTemplate}" />
</DataTemplate>
<DataTemplate x:Key="GroupHeader">
<DockPanel>
<Image Source="{Binding Name.ISO,
Converter={StaticResource CountryIdToFlagImageSourceConverter}}"
Stretch="None" Width="23" Height="18" RenderOptions.BitmapScalingMode="HighQuality" />
<TextBlock Text="{Binding Name.Name}" Margin="10,0,0,0" Foreground="Black" FontWeight="Bold"/>
<CheckBox Margin="5,0,0,0" HorizontalAlignment="Right" IsChecked="True" x:Name="PART_DisplayLeague"
Unchecked="CheckBoxCountry_Unchecked" Checked="CheckBoxCountry_Checked" />
</DockPanel>
</DataTemplate>
<Style TargetType="{x:Type GroupItem}" x:Key="containerStyle">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander IsExpanded="False" x:Name="ComboExpander"
Header="{TemplateBinding Content}"
HeaderTemplate="{StaticResource GroupHeader}" >
<ItemsPresenter />
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
as you can see in the GroupHeader I've a CheckBox, I'm trying to access to this CheckBox in the following way:
//current value of league.Country = "England";
//Iterate the available groups
foreach(CollectionViewGroup gp in CompetitionCombo.Items.Groups)
{
if(league.Country.Name == (gp.Name as Country).Name)
{
//Get the container
GroupItem container = CompetitionCombo.ItemContainerGenerator.ContainerFromItem(gp) as GroupItem;
//Get the control
var control = container.Template.FindName("PART_DisplayLeague", container);
control.IsChecked = true;
}
}
I get correctly the groups, but the control variable is even Null, seems that the code cannot find PART_DisplayLeague: the CheckBox associated to the Country in the header of the container.
Note:
the league is a simple implementation of the following:
public class League
{
public string Name { get; set; }
public Country Country { get; set; }
}
public class Country
{
public string Name { get; set; }
public string ISO { get; set; }
}
what am I doing wrong?
UPDATE
Forgot to add the collection implementation, in particular Competition:
public List<CheckedListItem<League>> Competitions = new List<CheckedListItem<League>>();
for add element to the collection:
Competitions.Add(new CheckedListItem<League>
{
IsChecked = true,
Item = new League { Name = "foo" }
});
is possible find an implementation of CheckedListItem here
as you can see Item is part of CheckedListItem.
UPDATE #2
Practice example of the situation:
Suppose that Competitions contains the following Items:
Competitions.Add(new CheckedListItem<League>
{
IsChecked = true,
Item = new League { Name = "foo", Country = new Country { Name = "Italy"}}
});
Competitions.Add(new CheckedListItem<League>
{
IsChecked = true,
Item = new League { Name = "foo2", Country = new Country { Name = "Italy"}}
});
Competitions.Add(new CheckedListItem<League>
{
IsChecked = true,
Item = new League { Name = "foo", Country = new Country { Name = "England"}}
});
the UI Structure will this:
[] Italy <- this is the GroupHeader sort by xaml
[] foo <- Items of the container
[] foo2
[] England
[] foo3
now what I'm asking for: When I check foo2 and foo the Italy CheckBox need to be Checked, also when I uncheck an Item contained in the Italy container, then, Italy must be unchecked.
This mean two thing:
if all items in the container are checked, then also the header checkbox need to be checked.
if almost one item in the container isn't checked, then the checkbox in the container must be unchecked 'cause this mean that not all the items associated to this country are checked.
My problem's that I can't find the CheckBox associated to the Country 'cause is generated by the Xaml, that's why I can't use a Binding property as suggested.

post the code that someone can just copy paste and reproduce your problem, then people can not only solve your problem but also suggest a better solution.
in the meantime, you can find your checkbox in the following manner:
var control = FindVisualChildren<CheckBox>(container);
Here you can find the method FindVisualChildren.
Edit: a full working example
public class League
{
public string Name { get; set; }
public Country Country { get; set; }
}
public class Country
{
public string Name { get; set; }
public string ISO { get; set; }
}
public class CheckedListItem<T>
{
private bool isChecked;
private T item;
public CheckedListItem() { }
public CheckedListItem(T item, bool isChecked = false)
{
this.item = item;
this.isChecked = isChecked;
}
public T Item
{
get { return item; }
set
{
item = value;
}
}
public bool IsChecked
{
get { return isChecked; }
set
{
isChecked = value;
}
}
}
public partial class MainWindow : Window
{
public ObservableCollection<CheckedListItem<League>> Competitions { get; set; }
public MainWindow()
{
InitializeComponent();
Competitions = new ObservableCollection<CheckedListItem<League>> { (new CheckedListItem<League>
{
IsChecked = true,
Item = new League { Name = "foo", Country = new Country { Name = "Italy" } }
}),
(new CheckedListItem<League>
{
IsChecked = true,
Item = new League { Name = "foo2", Country = new Country { Name = "Italy" } }
}),
(new CheckedListItem<League>
{
IsChecked = true,
Item = new League { Name = "foo", Country = new Country { Name = "England" } }
}) };
this.DataContext = this;
}
public void CheckBoxCountry_Checked(object sender, EventArgs args)
{
foreach (CollectionViewGroup gp in CompetitionCombo.Items.Groups)
{
//Get the container
GroupItem container = CompetitionCombo.ItemContainerGenerator.ContainerFromItem(gp) as GroupItem;
//Get the control
var control = FindVisualChildren<CheckBox>(container);
}
}
public IEnumerable<T> FindVisualChildren<T>(DependencyObject depObj) where T : DependencyObject
{
if (depObj != null)
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(depObj, i);
if (child != null && child is T)
yield return (T)child;
foreach (T childOfChild in FindVisualChildren<T>(child))
yield return childOfChild;
}
}
}
}
.xaml
<Window x:Class="WpfApp2.MainWindow"
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:WpfApp2"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.Resources>
<CollectionViewSource Source="{Binding Competitions}" x:Key="GroupedData">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="Item.Country" />
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
<DataTemplate x:Key="NormalItemTemplate">
<DockPanel>
<TextBlock Text="sdf" Margin="10,0,0,0" Foreground="Black" FontWeight="Bold"/>
</DockPanel>
</DataTemplate>
<DataTemplate x:Key="CombinedTemplate">
<ContentPresenter x:Name="Presenter"
Content="{Binding}"
ContentTemplate="{StaticResource NormalItemTemplate}"
/>
</DataTemplate>
<DataTemplate x:Key="GroupHeader">
<DockPanel>
<TextBlock Text="{Binding Name.Name}" Margin="10,0,0,0" Foreground="Black" FontWeight="Bold"/>
<CheckBox Margin="5,0,0,0" HorizontalAlignment="Right" IsChecked="True" x:Name="PART_DisplayLeague"
Checked="CheckBoxCountry_Checked" />
</DockPanel>
</DataTemplate>
<Style TargetType="{x:Type GroupItem}" x:Key="containerStyle">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander IsExpanded="False" x:Name="ComboExpander"
Header="{TemplateBinding Content}"
HeaderTemplate="{StaticResource GroupHeader}" >
<ItemsPresenter />
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Grid.Resources>
<ComboBox x:Name="CompetitionCombo"
ItemsSource="{Binding Source={StaticResource GroupedData}}"
ItemTemplate="{StaticResource CombinedTemplate}">
<ComboBox.GroupStyle>
<GroupStyle ContainerStyle="{StaticResource containerStyle}"
HeaderTemplate="{StaticResource GroupHeader}">
</GroupStyle>
</ComboBox.GroupStyle>
</ComboBox>
</Grid>
</Window>
I added NormalItemTemplate because you didn't provide the template.
When you check the checkbox in the UI, you will call the method CheckBoxCountry_Checked in mainwindow.cs and that method will find the checkbox inside all 3 comboboxes.

Related

How to get original value in TreeView?

I try get original value on WPF treeview.
Commonly, Treeview we get selected item' original value using
object Item = treeview1.SelectedItem;
MessageBox.Show(Item.ToString());
but, my attempts to get it using this method were unsuccessful.
If I try it, then I get a "WPFName+TreeItem" MessageBox
This is my code with WPF
C#
private void treeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
object temp = treeView.SelectedItem;
MessageBox.Show(temp.ToString());
}
private static IEnumerable<TreeItem> GetChannelTreeForTreeView(QueryRunner queryRunner)
{
List<ChannelTreeItem> channelTree = queryRunner.Utils.GetChannelTree(false);
foreach (ChannelTreeItem channelTreeItem in channelTree)
{
TreeItem treeViewItem = new TreeItem { Data = channelTreeItem.Channel };
FillTreeViewItem(treeViewItem, channelTreeItem);
yield return treeViewItem;
}
}
private static void FillTreeViewItem(TreeItem treeViewItem, ChannelTreeItem channelTreeItem)
{
foreach (ClientListEntry clientListEntry in channelTreeItem.Clients)
if (clientListEntry.Nickname.Contains("serveradmin from") == false)
{
treeViewItem.Children.Add(new TreeItem { Data = clientListEntry });
}
foreach (ChannelTreeItem childChannelTreeItem in channelTreeItem.Children)
{
TreeItem childTreeViewItem = new TreeItem { Data = childChannelTreeItem.Channel };
treeViewItem.Children.Add(childTreeViewItem);
FillTreeViewItem(childTreeViewItem, childChannelTreeItem);
}
}
public class TreeItem
{
public object Data { get; set; }
public List<TreeItem> Children { get; private set; }
public TreeItem()
{
Children = new List<TreeItem>();
}
}
WPF
<TreeView x:Name="treeView" HorizontalAlignment="Left"
Height="265" VerticalAlignment="Top" Width="353"
SelectedItemChanged="treeView_SelectedItemChanged"
MouseUp="treeView_MouseUp">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type self:ViewItemWPF+TreeItem}" ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal">
<ContentPresenter Content="{Binding Data}"/>
</StackPanel>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type HelperClasses:ChannelListEntry}">
<StackPanel Orientation="Horizontal">
<Border Background="Green" Width="8" Height="12" BorderBrush="#00000000"></Border>
<TextBlock Text="{Binding Path=Name}"/>
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type HelperClasses:ClientListEntry}" >
<StackPanel Orientation="Horizontal">
<Border Background="DarkBlue" Width="8" Height="12" BorderBrush="#00000000"></Border>
<TextBlock Text="{Binding Path=Nickname}" Foreground="blue" />
</StackPanel>
</DataTemplate>
</TreeView.Resources>
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="True" />
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
Can I get original value using foreach or others methods?
You could cast the SelectedItem property to a TreeItem and then cast its Data property to the appropriate type. You can then access any properties of your classes as usual.
It is unclear what type "Channel" is but the following sample code should give you the idea:
private void treeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
TreeItem temp = treeView.SelectedItem as TreeItem;
if (temp != null)
{
ClientListEntry clientListEntry = temp.Data as ClientListEntry;
if (clientListEntry != null)
{
//Data is a ClientListEntry
//...
return;
}
Channel channel = temp.Data as ClientListEntry;
if (channel != null)
{
//...
}
}
MessageBox.Show(temp.ToString());
}

Can columns in WPF datagrid hold different types of data

I'm trying to display different rows in a datagrid with different cellcontent sometimes.
I have different classes for the different rows, for example
Class 1:
Name - Description - Checkbox
Class 2:
Name - Description - Textbox(user input at runtime) - Checkbox
Class 3
Name - Textbox(user input at runtime)
The classes are related by inheritance so I can use them within the same observablecollection.
I want to display these in a datagrid based on which class I chose to add, for example:
ObservableCollection<Rowitem> rowitems = new ObservableCollection<Rowitem>();
rowitems.Add(new Class1("Tom", "Nice", false));
rowitems.Add(new Class2("John", "Strange", Empty textbox , true));
rowitems.Add(new Class3("Roger", Empty Textbox));
.. meaning I would like the datagrid to display an empty textbox in the third column on the second row where there is a checkbox in the first row and nothing in the third row. Is this possible?
Here is my suggestion:
<Window x:Class="DataGridDynamicCellView.MainWindow"
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:local="clr-namespace:DataGridDynamicCellView"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="MainWindow"
Width="525"
Height="350"
mc:Ignorable="d">
<Window.DataContext>
<local:DynamicCellsDataContext />
</Window.DataContext>
<Grid>
<DataGrid ItemsSource="{Binding DataGridSource}">
<DataGrid.Resources>
<DataTemplate DataType="{x:Type local:PresentedByCheckBox}">
<Grid HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<CheckBox HorizontalAlignment="Center"
VerticalAlignment="Center"
IsChecked="{Binding IsChecked,
UpdateSourceTrigger=PropertyChanged}" />
</Grid>
</DataTemplate>
<DataTemplate DataType="{x:Type local:PresentedByTextBox}">
<Grid HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<TextBlock HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{Binding HelloWorld,
UpdateSourceTrigger=PropertyChanged}" />
</Grid>
</DataTemplate>
<DataTemplate DataType="{x:Type local:PresentedByComplexBox}">
<StackPanel HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Orientation="Horizontal">
<Ellipse Height="10" Width="10" Fill="Pink"/>
<CheckBox HorizontalAlignment="Center"
VerticalAlignment="Center"
IsChecked="{Binding Checked,
UpdateSourceTrigger=PropertyChanged}" />
<TextBlock HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{Binding HelloWorld,
UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>
</DataTemplate>
<Style TargetType="{x:Type DataGridCell}">
<Setter Property="BorderBrush" Value="Green" />
<Setter Property="BorderThickness" Value="2" />
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<ContentControl Content="{Binding}" />
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</DataGrid.Resources>
</DataGrid>
</Grid></Window>
MVVM view model:
public class DynamicCellsDataContext:BaseObservableObject
{
public DynamicCellsDataContext()
{
DataGridSource = new ObservableCollection<object>
{
new PresentedByTextBox("Hello world!!!"),
new PresentedByCheckBox(true),
new PresentedByComplexBox("Hello world!!!", true),
};
}
public ObservableCollection<object> DataGridSource { get; set; }
}
public class PresentedByComplexBox:BaseObservableObject
{
private string _helloWorld;
private bool _checked;
public string HelloWorld
{
get { return _helloWorld; }
set
{
_helloWorld = value;
OnPropertyChanged();
}
}
public bool Checked
{
get { return _checked; }
set
{
_checked = value;
OnPropertyChanged();
}
}
public PresentedByComplexBox(string helloWorld, bool isChecked)
{
HelloWorld = helloWorld;
Checked = isChecked;
}
}
public class PresentedByCheckBox:BaseObservableObject
{
private bool _isChecked;
public bool IsChecked
{
get { return _isChecked; }
set
{
_isChecked = value;
OnPropertyChanged();
}
}
public PresentedByCheckBox(bool isChecked)
{
IsChecked = isChecked;
}
}
public class PresentedByTextBox:BaseObservableObject
{
private string _helloWorld;
public string HelloWorld
{
get { return _helloWorld; }
set
{
_helloWorld = value;
OnPropertyChanged();
}
}
public PresentedByTextBox(string helloWorld)
{
HelloWorld = helloWorld;
}
}
The BaseObservableObject class:
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;
}
}
That's all, let me know in case you will need more examples.
Best regards.
You can do this using DataTemplates:
Just add them to the Resources of your Grid:
<Grid.Resources>
<DataTemplate DataType="{x:Type local:Class1}">
<-- Template for class1 -->
</DataTemplate>
<DataTemplate DataType="{x:Type local:Class2}">
<-- Template for class2 -->
</DataTemplate>
</Grid.Resources>

How to insert multiple GroupDescription?

I've made an application that allow to get the live result of a match.
Now the organization of each match is this:
Nation->League->Match list
So I've the Nation that is the container of the League, and the League is the container of the Match. What I did firstly was create a XAML structure that allow me to achieve a container result as said before:
<Window x:Class="GroupBox_Header.MainWindow"
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:GroupBox_Header"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid Margin="10">
<ListView Name="lvUsers">
<ListView.View>
<GridView>
<GridViewColumn Header="Home Team" Width="50" DisplayMemberBinding="{Binding HomeTeam}" />
<GridViewColumn Header="Away Team" Width="50" DisplayMemberBinding="{Binding AwayTeam}" />
</GridView>
</ListView.View>
<ListView.GroupStyle>
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Expander IsExpanded="True">
<Expander.Header>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" FontWeight="Bold" Foreground="Gray" FontSize="22" VerticalAlignment="Bottom" />
<TextBlock Text="{Binding ItemCount}" FontSize="22" Foreground="Green" FontWeight="Bold" FontStyle="Italic" Margin="10,0,0,0" VerticalAlignment="Bottom" />
<TextBlock Text=" Items" FontSize="22" Foreground="Silver" FontStyle="Italic" VerticalAlignment="Bottom" />
</StackPanel>
</Expander.Header>
<ItemsPresenter />
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Expander IsExpanded="True">
<Expander.Header>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding League}" FontWeight="Bold" Foreground="Gray" FontSize="22" VerticalAlignment="Bottom" />
</StackPanel>
</Expander.Header>
<ItemsPresenter />
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</ListView.GroupStyle>
</ListView>
</Grid>
How you can see there is a ListView that have a GroupStyle definition, in this I save all the Nation. You can try the XAML in your demo solution and check the result.
After this, I've created the struct that allow me to store the value:
public struct League
{
public string Name { get; set; }
}
public struct Country
{
public string Name { get; set; }
public League League;
}
Is very pretty simple, a League struct for save all the leagues and a Country struct for the nations. I've valorized the Country in this way:
List<Country> items = new List<Country>();
items.Add(new Country() { Name = "Italy", League = { Name = "Serie A"} });
items.Add(new Country() { Name = "Italy", League = { Name = "Serie B" } });
items.Add(new Country() { Name = "England", League = { Name = "Premiere League" } });
items.Add(new Country() { Name = "Spain", League = { Name = "Primeira Division" } });
lvUsers.ItemsSource = items;
So I actually have in list Italy (2), England, Spain. I then inserted items in the ItemsSource of the ListView (the XAML control).
For create the header group as the Nation name, I've used a CollectionView:
CollectionView view = (CollectionView)CollectionViewSource.GetDefaultView(lvUsers.ItemsSource);
PropertyGroupDescription groupNations = new PropertyGroupDescription("Name");
This code allow me to create multiple headers, and the result is exactly what I want. I also did the same for the League, 'cause what I need is insert as a sub-header the League name in Nation container (header):
view.GroupDescriptions.Add(groupNations);
PropertyGroupDescription groupCompetions = new PropertyGroupDescription("League");
view.GroupDescriptions.Add(groupNations);
but actually this is the result:
How you can see only the nation header appear, but the League header doesn't appear inside the Nation header. What am I doing wrong?
You can "fix" it by making League a property (add getter and setter - WPF bindings attach to properties, not fields), making the second group description group by "League.Name" instead of just "League" and make the second group style TextBlock binding bind to Name, not League.
Then you get this:
But I would say you are approaching this the wrong way and modeling your data in a really weird manner. If the ListView is intended to list Matches, then you should bind to a collection of Match objects (or a view over them), and each Match should reference its parent League, and each League should reference its parent Country.
Alternatively, use a tree instead of a list, and then have a Country with a collection of child Leagues, and each League with a collection of child Matches.
Or have both - a League that contains a collection of Matches, but each match has a parent League reference, and similar for Country/League.
The way it is in your code is kind of weird.
Also, I don't understand why you use structs for your objects instead of classes.
Changes to code behind (note, I am not saying this is a good way to do it, see above):
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
List<Country> items = new List<Country>();
items.Add(new Country() { Name = "Italy", League = new League { Name = "Serie A" } }); // Added "new League"
items.Add(new Country() { Name = "Italy", League = new League { Name = "Serie B" } });
items.Add(new Country() { Name = "England", League = new League { Name = "Premiere League" } });
items.Add(new Country() { Name = "Spain", League = new League { Name = "Primeira Division" } });
lvUsers.ItemsSource = items;
CollectionView view = (CollectionView)CollectionViewSource.GetDefaultView(lvUsers.ItemsSource);
PropertyGroupDescription groupNations = new PropertyGroupDescription("Name");
view.GroupDescriptions.Add(groupNations);
PropertyGroupDescription groupCompetions = new PropertyGroupDescription("League.Name"); // Changed "League" to "League.Name"
view.GroupDescriptions.Add(groupCompetions); // Fixed the variable name here
lvUsers.ItemsSource = view; // Added, you probably have it but didn't post it
}
}
public struct League
{
public string Name { get; set; }
}
public struct Country
{
public string Name { get; set; }
public League League { get; set; } // added getter and setter
}
Changes to XAML, just 1 line:
<TextBlock Text="{Binding Name}" FontWeight="Bold" Foreground="Gray" FontSize="22" VerticalAlignment="Bottom" />
{Binding Name} is {Binding League} in your original code.

Add textbox in Checkboxlist for particular item checked WPF

I am creating a application using WPF. I am able to bind Checkboxlist by using the below code:
XAML:
<DataTemplate x:Key="defaultTemplate">
<dxe:CheckEdit x:Name="lstcheckbox" Checked="lstcheckbox_Checked_1" Content="{Binding Name}" IsChecked="{Binding Path=Checked}" />
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Path=Checked}" Value="true">
<Setter TargetName="lstcheckbox" Property="ContentTemplate" Value="{DynamicResource Template1}"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
<DataTemplate x:Key="Template1">
<TextBox Width="100"></TextBox>
</DataTemplate>
<local:SomeTemplateSelector x:Key="SomeTemplateSelector" DefaultTemplate="{StaticResource defaultTemplate}"
Template1="{StaticResource Template1}">
</local:SomeTemplateSelector>
</Window.Resources>
<Grid>
<dxd:DockLayoutManager x:Name="dockManager" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" ScrollViewer.HorizontalScrollBarVisibility="Visible" ScrollViewer.VerticalScrollBarVisibility="Visible">
<dxd:LayoutGroup Caption="Review Checklist" Orientation="Vertical" ShowCaption="True" GroupBorderStyle="Group" CaptionAlignMode="AlignInGroup">
<dxd:LayoutControlItem>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<TextBlock Grid.Column="0" Grid.Row="0" Margin="8,5,5,5">Safety Equipment</TextBlock>
<!--<ListView x:Name="lstSafEquip" Grid.Column="0" Grid.Row="1" BorderThickness="0" Margin="5,5,5,5" SelectionMode="Multiple" ItemsSource="{Binding}" ItemTemplateSelector="{StaticResource SomeTemplateSelector}" ItemTemplate="{StaticResource ItemDataTemplate}" IsSynchronizedWithCurrentItem="true" ScrollViewer.VerticalScrollBarVisibility="Auto" MaxHeight="200" />-->
<ListView x:Name="lstSafEquip" Grid.Column="0" Grid.Row="1" BorderThickness="0" Margin="5,5,5,5" ItemsSource="{Binding}" ItemTemplateSelector="{StaticResource SomeTemplateSelector}" IsSynchronizedWithCurrentItem="true" ScrollViewer.VerticalScrollBarVisibility="Auto" MaxHeight="200" />
</Grid>
</dxd:LayoutControlItem>
</dxd:LayoutGroup>
</dxd:DockLayoutManager>
</Grid>
CS:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
BindCheckBoxs(lstSafEquip);
}
private void BindCheckBoxs(ListView lst)
{
try
{
ObservableCollection<Data> Source = new ObservableCollection<Data> { new Data(1, "Apple"), new Data(2, "Mango"), new Data(3, "Others"), new Data(3, "Banana"), new Data(3, "Grapes") };
lst.ItemsSource = Source;
}
catch (Exception ex)
{
throw new Exception(ex.Message.ToString());
}
}
}
public class SomeTemplateSelector : DataTemplateSelector
{
public DataTemplate Template1 { get; set; }
public DataTemplate DefaultTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
Data _Item = (Data)item;
if (_Item.Name == "Others")
{
return Template1;
}
return DefaultTemplate;
}
}
DATA.CS:
public class Data
{
public int ID { get; set; }
public string Name { get; set; }
public bool Checked { get; set; }
public Data(int _ID, string _Name)
{
ID = _ID;
Name = _Name;
Checked = false;
}
}
PROBLEM:
I want to add textbox just below the checkboxlist item Others when user will checked this item.
By using the above code trigger enter textbox for each item when i checked.
please let me know where i am going wrong. I will try my best to provide more info if needed.
From what i understood from your post and comments:
Add a TextBox as Barptad suggested but use a BooleanToVisibilityCollapsed Converter as stated below:
BooleanToVisibilityCollapsedConverter.cs:
public class BooleanToVisibilityCollapsedConverter : IValueConverter {
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
return ((bool) value) ? Visibility.Visible : Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture) {
//never used
return null;
}
}
XAML:
<DataTemplate x:Key="defaultTemplate">
<StackPanel>
<dxe:CheckEdit x:Name="lstcheckbox" Checked="lstcheckbox_Checked_1" Content="{Binding Name}" IsChecked="{Binding Path=Checked}" />
<TextBox Visibility="{Binding IsChecked, ElementName=lstcheckbox}"/>
</StackPanel>
</DataTemplate>
If you use Visibility.Hidden the textbox is not visible but still takes place.
After merging all suggestions and comments. I reached on the solution.
XAML:
Added textbox in default template
<DataTemplate x:Key="defaultTemplate">
<StackPanel>
<dxe:CheckEdit x:Name="lstcheckbox" Checked="lstcheckbox_Checked_1" Unchecked="lstcheckbox_Checked_1" Content="{Binding Name}" IsChecked="{Binding Path=Checked}" />
<TextBox Name="txtOther" Visibility="{Binding Path=Visible}" Text="test" Width="100"></TextBox>
</StackPanel>
</DataTemplate>
XAML.CS:
private void lstcheckbox_Checked_1(object sender, RoutedEventArgs e)
{
Data some = (sender as CheckEdit).DataContext as Data;
if (some.Name.Contains("Others"))
{
some.Visible = ToggleVisibility(some.Visible);
}
}
private Visibility ToggleVisibility(Visibility visibility)
{
return visibility == Visibility.Visible ? Visibility.Collapsed : Visibility.Visible;
}
DATA.CS:
Added visible property and inherited with INotifyPropertyChanged to track change in property in Data.cs
public class Data : INotifyPropertyChanged
{
private Visibility _visible;
public int ID { get; set; }
public string Name { get; set; }
public bool Checked { get; set; }
//public Visibility Visible { get; set; }
public Visibility Visible
{
get { return this._visible; }
set
{
this._visible = value;
RaisePropertyChanged("Visible");
}
}
public Data(int _ID, string _Name,Visibility _visible)
{
ID = _ID;
Name = _Name;
Visible = _visible;
}
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string name)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
SomeTemplateSelector.CS
public class SomeTemplateSelector : DataTemplateSelector
{
public DataTemplate OthersTemplate { get; set; }
public DataTemplate DefaultTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
return DefaultTemplate;
}
}
Hope this will help others.
Correct Me If I am worng!
U want to show textbox only under "Others" item when it is checked.
For the scenario mentioned above u need to have two different templates that changes on the value of item.
For e.g if the item is "Others" it should have template that have a text box just below check box which is only shown when checkbox is checked otherwise collapsed.
for the second type of items it should have different template even if it is checked no textbox should be displayed under it.
(Step 1) Create a Template Selector
public class ValueDataTemplateSelector : DataTemplateSelector
{
public DataTemplate DefaultTemplate { get; set; }
public DataTemplate OthersTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, System.Windows.DependencyObject container)
{
DataTemplate template = DefaultTemplate;
CheckList data = item as CheckList;
if (data.Description.Contains("Others"))
{
template = OthersTemplate;
}
return template;
}
}
Reference assembly in your file.
(Step 3) Create two templates as given below
<Window.Resources>
<DataTemplate x:Key="DefaultTemplate">
<StackPanel>
<CheckBox IsChecked="{Binding Path=IsChecked}" Content="{Binding Path=Description}"></CheckBox>
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="OthersTemplate">
<DataTemplate.Resources>
<Style TargetType="TextBox">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsChecked}" Value="false">
<Setter Property="Visibility" Value="Collapsed"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</DataTemplate.Resources>
<StackPanel>
<CheckBox IsChecked="{Binding Path=IsChecked}" Content="{Binding Path=Description}"></CheckBox>
<TextBox x:Name="othersTextBox">
</TextBox>
</StackPanel>
</DataTemplate>
<local:ValueDataTemplateSelector x:Key="CheckListTemplateSelector"
DefaultTemplate="{StaticResource DefaultTemplate}"
OthersTemplate="{StaticResource OthersTemplate}"/>
</Window.Resources>

How can i use expander as a child item of treeview in WPF

I want to show a custom set of repeating datas
Class M
prop1 // treeview heading
List<String> // treeview items
List<ClassSub> [ prop 1, porp2 ,List<String> sub items] //as an expander with List<String> as expander items
Class M
prop1 // treeview heading
prop2 // treeview items
List<ClassSub>[prop1 ,prop2,List<String>...]//as an expander
Can i use a Treeview + Expander combination to arrange this complete set of data?
Or do i need to use a Codeplex librarys like http://complexdatatemplates.codeplex.com [But i dont see a clear documentation on it]
here is how I offer to solve your issue
xaml
<TreeView ItemsSource="{Binding TreeItems}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding SubItems}">
<Expander Header="{Binding Header}">
<ItemsControl Margin="25,0,0,0" ItemsSource="{Binding TreeItems}" />
</Expander>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
Item class
class Item
{
public string Header { get; set; }
public List<string> TreeItems { get; set; }
public List<Item> SubItems { get; set; }
}
View Model
class ViewModel
{
public ViewModel()
{
TreeItems = new ObservableCollection<Item>();
string[] data = new string[]{
"Subchapter 1","Subchapter 2",
};
Item item = new Item()
{
Header = "Sub Getting Started",
TreeItems = new List<string>(data)
};
TreeItems.Add(new Item()
{
Header = "Getting Started 1",
SubItems = new List<Item>(new Item[] { item }),
TreeItems = new List<string>(data)
});
TreeItems.Add(new Item()
{
Header = "Getting Started 2",
SubItems = new List<Item>(new Item[] { item }),
TreeItems = new List<string>(data)
});
TreeItems.Add(new Item()
{
Header = "Getting Started 3",
SubItems = new List<Item>(new Item[] { item }),
TreeItems = new List<string>(data)
});
}
public ObservableCollection<Item> TreeItems { get; private set; }
}
result
In example above I haven't styled the Expander to match the one in screenshot posted in question. let me know if that is also required.
EDIT
as discussed, to display SubChapter1.1. and 1.2 to be visible by default or expand any other tree view item by default
start by defining a style for TreeViewItem in the resources
<TreeView.Resources>
<Style TargetType="TreeViewItem">
<Setter Property="IsExpanded"
Value="{Binding IsExpanded}" />
</Style>
</TreeView.Resources>
add a property IsExpanded to BookChapter class
public class BookChapter
{
public string name { get; set; }
public string id { get; set; }
public List<BookPage> pages { get; set; }
//public List<Enrichment> enrichment { get; set; }
public List<SubChapter> chapters { get; set; }
public bool IsExpanded { get; set; }
}
then set IsExpanded to true if you want it to be initially expanded
BookChapter cha = new BookChapter { name = "Chapter Intro", pages = pags, IsExpanded=true };
result
you can control other tree items in a similar manner too.
EDIT 2
sample for the same using Listbox and data templates
<Grid>
<Grid.Resources>
<DataTemplate x:Key="chapterTemplate">
<Expander Header="{Binding name}" IsExpanded="{Binding IsExpanded}">
<StackPanel>
<ItemsControl Margin="25,0,0,0"
ItemsSource="{Binding pages}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding Path=label}">
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<ListBox ItemsSource="{Binding chapters}"
BorderBrush="{x:Null}"
Margin="20,0,0,0" />
</StackPanel>
</Expander>
</DataTemplate>
<DataTemplate DataType="{x:Type l:BookChapter}">
<ContentControl Content="{Binding}"
ContentTemplate="{StaticResource chapterTemplate}" />
</DataTemplate>
<DataTemplate DataType="{x:Type l:SubChapter}">
<ContentControl Content="{Binding}"
ContentTemplate="{StaticResource chapterTemplate}" />
</DataTemplate>
<DataTemplate DataType="{x:Type l:BookPage}">
<Button Content="{Binding Path=label}" />
</DataTemplate>
</Grid.Resources>
<ListBox ItemsSource="{Binding}"
Name="TOCView" />
</Grid>
result
with this approach you may not need to template the tree view item for the arrow. just template the expander as you wish
as a suggestion you can reuse BookChapter class as subchapters instead of creating a new class for SubChapter unless necessary.

Categories