I've been following this guide (among other resources) and have the ListBox grouping my list of Users successfully. The problem now is two-fold;
The header of each group is not displaying what it is grouping by (at present, just an int).
I would like, if possible, for the header to be a lookup. (I'm using Entity Framework, so this could be made redundant by using an Include(u => u.Ref_Department) on the query to get the list of Users.)
The data is as follows;
Users
idUser
id_Department
Name
Department
idDepartment
DepartmentName
The XAML;
<Grid>
<Grid.Resources>
<CollectionViewSource x:Key="lsUsers" Source="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}}, Path=_Users}">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="id_Department"/>
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
</Grid.Resources>
<ListBox ItemsSource="{Binding Source={StaticResource lsUsers}}">
<ListBox.GroupStyle>
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Expander Header="{Binding id_Department}" IsExpanded="True">
<ItemsPresenter/>
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</ListBox.GroupStyle>
</ListBox>
</Grid>
_Users is a List<Users> property of the class, which is obviously working fine anyway, it's just the header that is falling down for some reason and I don't really understand why.
There is a highlight/warning on the <Expander Header... line under id_Department that says "Cannot resolve symbol 'id_Department' due to unknown DataContext"
Update
If I change the <Expander Header... binding to Name, it displays the id_Department. Not sure how/why that works and I'm not sure that it'll help with Point 1 above.
while Using GroupStyle you have only the following Binding Propoerties:
Name: which is the Name of group property that make the grouping based on
ItemCount: which is the Rows Count of each grouping record
and if you want another property based on grouping item you should use Converter but when using:
{Binding Converter="{StaticResource yourConverter}"
consider that the value of Converter is CollectionViewGroup which is the list of objects or rows that is in the grouped base item.
As #safi implies, because the GroupItem is what is being styled you only have the Name and ItemCount properties to bind to.
It sounds like you are trying to group by department but want to see the department name instead of the ID. Therefore you should add a string Department property to your Users class and group on that instead of the department ID property.
You can control the ordering of the groups by changing the SortDescriptions of the collection view. For example, if you want to group by department name but still see departments listed in ID order rather than alphabetically then group by the name and also sort by the ID, like this:
ICollectionView view = CollectionViewSource.GetDefaultView(_Users);
view.GroupDescriptions.Add(new PropertyGroupDescription("DepartmentName"));
view.SortDescriptions.Add(new SortDescription("DepartmentId", ListSortDirection.Ascending));
view.SortDescriptions.Add(new SortDescription("UserName", ListSortDirection.Ascending));
I've used different property names here but you get the idea.
Related
I have a problem that is not addressed by the numerous articles on MVVM grouping that I have read.
I am writing a WPF application. Here are some excepts from classes that are relevant to my question - first the MainViewModel:
public class MainViewModel
{
public ObservableCollection<Recipe_OverViewModel> RecipeOverViews {get ; set;}
....[omitted extraneous lines]....
The class that is used as the observable collection of Recipes in the MainViewModel:
public class Recipe_OverViewModel
{
public Recipe TargetRecipe { get; set; }
public override string ToString()
{
return TargetRecipe.Parent_Name;
}
....[omitted extraneous lines]....
and The Class that is taken from the database, that is the actual Recipe
public partial class Recipe
{
[Required]
[StringLength(1000)]
public string Parent_Name { get; set; }
[Required]
[StringLength(60)]
public string Recipe_Name { get; set; }
public override string ToString()
{
return Parent_Name;
}
public int CompareTo(object obj)
{
return Parent_Name.CompareTo(((Recipe)obj).Parent_Name);
}
....[omitted extraneous lines]....
Each class has more properties and methods and so on, but these are enough to explain what I am asking.
The Recipe_OverViewModel is the view model for a control (Recipe_OverView) that displays the properties of the recipe. In the MainViewModel, I have the following xaml (extracted from the larger file):
<Window x:Class="RecipeApp.UI.MainWindow"
....[omitted extraneous lines]....
xmlns:vm="clr-namespace:RecipeApp.UI.ViewModel"
>
<Window.Resources>
<DataTemplate DataType="{x:Type vm:Recipe_OverViewModel}" x:Key="Recipe_DT" x:Name="Recipe_DT">
<control:Recipe_OverView Width="{Binding ActualWidth,ElementName=ListWidth}"/>
</DataTemplate>
<CollectionViewSource x:Key="SortedRecipeOverViews" Source="{Binding RecipeOverViews}">
<CollectionViewSource.SortDescriptions>
<scm:SortDescription PropertyName="TargetRecipe"/>
</CollectionViewSource.SortDescriptions>
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="TargetRecipe"/>
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
</Window.Resources>
....[omitted extraneous lines]....
<ListView ItemsSource="{Binding Source={StaticResource SortedRecipeOverViews}}"
ItemTemplate="{StaticResource Recipe_DT}">
</ListView>
This list view correctly displays the list of recipes in the listview, with each row containing the Recipe_OverView control. However, I cannot get the grouping to work correctly. I would like to group the listview by the Parent_Name property of the Recipe associated with each Recipe_OverViewModel. My attempt looked like this, following the Microsoft HowTo:
<ListView Grid.Row="1" Grid.Column="1"
ItemsSource="{Binding Source={StaticResource SortedRecipeOverViews}}"
ItemTemplate="{StaticResource Recipe_DT}"
>
<ListView.GroupStyle>
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander IsExpanded="true">
<Expander.Header>
<TextBlock Text="{Binding Path=ParentName}" />
</Expander.Header>
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</ListView.GroupStyle>
</ListView>
However, all I get from that is as many empty (no ParentName populated) headers as there are recipes in the collection and a Binding Failure that tells me the "ParentName property not found on object of type CollectionViewGroupInternal." I get the expander buttons, but there is nothing within the expanded groups:
I thought I understand that that means that the binding is looking in the Recipe_OverViewModel for the ParentName property, but even when I added this as a property in the Recipe_OverViewModel and populated it, I still got this error, so now I am confused and have the following questions:
Where is the binding on the ListView actually looking?
How should I direct it to look at the Recipe_OverViewModel.TargetRecipe.ParentName (or is it impossible)?
I would really appreciate help on this matter, so many articles take so much simpler examples, and I cannot work out how to extend it to my case!
Where is the binding on the ListView actually looking?
It looks for a property of the CollectionViewGroupInternal class.
This class has a Name property that returns the value of the property that you group by, i.e. TargetRecipe, and an Items property that returns the collection of objects that belongs to the current group.
So, if I understand your setup correctly, you could try to bind to the Parent_Name property of the first item in the group:
<Expander.Header>
<TextBlock Text="{Binding Items[0].Parent_Name}" />
</Expander.Header>
The ItemsSource is bound to the SortedRecipeOverViews, which in turn is bound to the RecipeOverViews collection.
The item type of this collection is Recipe_OverViewModel.
And this type doesn't have a ParentName property.
There is a Parent_Name property, BUT not in the Recipe_OverViewModel type, but in the Recipe type.
And the Recipe_OverViewModel type has a property of this type.
In general, you have some kind of mess of types, their names and their properties, binding paths.
Perhaps you copied something wrong into the topic?
Based on my own guess, try applying a binding like this:
<Expander.Header>
<TextBlock Text="{Binding Path=TargetRecipe.Parent_Name}" />
</Expander.Header>
I'm currently working on a WPF .NET 4.7 application and I use Infragistics WPF controls version 18.
I need to create a custom XamComboEditor which has a XamDataTree inside. Thus a ComboBox with a Tree selection inside.
The Tree selection works fine without the XamComboEditor and looks like this:
<iWPF:XamDataTree ItemsSource="{Binding Locations}">
<iWPF:XamDataTree.GlobalNodeLayouts>
<iWPF:NodeLayout Key="Locations" TargetTypeName="LocationViewModel" DisplayMemberPath="Name"/>
<iWPF:NodeLayout Key="ChildLocations" TargetTypeName="string"/>
</iWPF:XamDataTree.GlobalNodeLayouts>
</iWPF:XamDataTree>
My XamDataTree is bound to an observable collection Locations:
public ObservableCollection<LocationViewModel> Locations { get; set; } = new ObservableCollection<LocationViewModel>();
public class LocationViewModel
{
public int Id { get; set; }
public string Name { get; set; }
public List<LocationViewModel> ChildLocations { get; set; } = new List<LocationViewModel>();
}
I need to use the style setter on my XamComboEditor to put the XamDataTree inside the combobox.
My problem is now, I don't know how to achieve this, or how to pass the context from the XamComboEditor further to the XamDataTree.
I tried the following, in vain:
<iWPF:XamComboEditor ItemsSource="{Binding Locations}">
<iWPF:XamComboEditor.ComboBoxStyle>
<Style TargetType="ComboBox">
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<iXaml:XamDataTree ItemsSource="{Binding .}">
<iXaml:XamDataTree.GlobalNodeLayouts>
<iXaml:NodeLayout Key="Locations" TargetTypeName="LocationViewModel" DisplayMemberPath="{Binding Name}"/>
<iXaml:NodeLayout Key="ChildLocations" TargetTypeName="string"/>
</iXaml:XamDataTree.GlobalNodeLayouts>
</iXaml:XamDataTree>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</iWPF:XamComboEditor.ComboBoxStyle>
</iWPF:XamComboEditor>
Do you know how to solve this issue? Do you know how to pass the data context from the parent control to, let's say, the child control? Or rather, how to put the XamDataTree inside the XamComboEditor?
If I understood this correctly, the DataContext of your XamlDataTree is no what you expect it to be (the Locations bound in you XamComboEditor).
One way to solve this problem is to specify the source of the path in your Binding markup extension.
You can use the {x:Reference ...} markup extension to reference a named controled in your control tree.
<iWPF:XamComboEditor x:Name="comboEditor" ItemsSource="{Binding Locations}">
<iWPF:XamComboEditor.ComboBoxStyle>
<Style TargetType="ComboBox">
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<iXaml:XamDataTree ItemsSource="{Binding Source={x:Reference Name=comboEditor}, Path=DataContext.Locations}">
<iXaml:XamDataTree.GlobalNodeLayouts>
<iXaml:NodeLayout Key="Locations" TargetTypeName="LocationViewModel" DisplayMemberPath="{Binding Name}"/>
<iXaml:NodeLayout Key="ChildLocations" TargetTypeName="string"/>
</iXaml:XamDataTree.GlobalNodeLayouts>
</iXaml:XamDataTree>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</iWPF:XamComboEditor.ComboBoxStyle>
</iWPF:XamComboEditor>
You can also achieve this without naming your controls with the RelativeSource property in the Binding (something like RelativeSource={RelativeSource AncestorType=iWPF:XamComboEditor}).
I have a menu that I populate by setting its ItemSource to a list of the type TblCategory in the view model. The TblCategory type is just a class that has an Id and a Name variable in it.
I set the DisplayMemberPath of the menu to "Name", so I have a list of MenuItems properly named and presented, however I would like to do one more thing that I don't know how to do:
id like Each MenuItem to have the Id of the category in its Tag.
I think I would have to modify the Menu.ItemContainerStyle to bind to something, but I need some guidance.
Picture for clarification
Also, the TblCategory class and table
What I would like to achieve
Any help is very much appreciated, thank you.
<Menu ItemsSource={Binding ...}>
<Menu.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Tag" Value="{Binding Path=Id}"/>
</Style>
</Menu.ItemContainerStyle>
</Menu>
I am trying to create a data grid based on the items of a list of objects.
I have the following class:
class BookCopies
{
private string bookTitle;
private int bookNumbers;
public string BookTitle
{
get;
set;
}
public int BookNumbers
{
get;
set;
}
}
and then i populate a classical list with items of this type
List<BookCopies> booksWithCopies = new List<BookCopies>();
//...
return booksWithCopies;
Basically this list will contain items like ("The Adventures of Tom Sawyer", 3) , ("Infinite Jest" , 8) and so on... It keeps a book title and the number of books from the bookstore.
I know that this list has to be converted to a ObservableCollection and raise the NotifyPropertyChanged in order to have the mvvm pattern as it should.
Now what I am trying is to use this book list to set the content of the grid view. So the book title to become the column header and below it to have the number of books. Basically the grid to look something like this:
Most of the examples where hard coding the header of the column, now I am trying to read it from a list, and also to bound the content of the grid from the same list.
I am quite new to WPF and also MVVM and first I am thinking if this is possible (it will be nice to... since I have all the data that I need already in a list) and if that is possible to send me some examples or quide me through a little bit about how to implement this.
<DataGrid x:Name="Dgrd" HeadersVisibility="Row" VerticalGridLinesBrush="LawnGreen" CanUserAddRows="False">
<DataGrid.Resources>
<Style TargetType="DataGridRowHeader">
<Setter Property="BorderBrush" Value="DarkViolet"/>
<Setter Property="BorderThickness" Value="1"/>
</Style>
<Style TargetType="DataGridCellsPresenter">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="DataGridCellsPresenter">
<StackPanel>
<Label Padding="5" Content="{Binding BookTitle}" BorderThickness="0 0 0 1" FontWeight="Bold" BorderBrush="DarkViolet" Background="Transparent"/>
<TextBlock Padding="5" Text="{Binding BookNumbers}"/>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</DataGrid.Resources>
<DataGrid.ItemsPanel>
<ItemsPanelTemplate>
<DataGridRowsPresenter Orientation="Horizontal" />
</ItemsPanelTemplate>
</DataGrid.ItemsPanel>
</DataGrid>
You can not achieve what you are trying to because DataGrid does not work that way. The bounded collection on the DataGrid (itemsource) is used to populate the row of the DataGrid.
However there are others trying to do the same thing you are. Take a look on WPF horizontal DataGrid, the result is similar to what you are describing (except the column names on the left). The resulting solution requires some coding.
Another resource you can look into is the Displaying vertical rows in a DataGrid from Code Project website.
Today I'm having trouble passing values from a parent control down to the properties of a child control in a list.
I have a custom control which I've made which functions as a Thumbnail Check Box. Essentially it's just a checkbox wrapped around an image with some nice borders. It's all wrapped up into a DLL and deployed as a custom control
If I want to use a single instance of the control, I can do so like this...
<tcb:ThumbnailCheckBox IsChecked="True"
ImagePath="D:\Pictures\123.jpg"
CornerRadius="10"
Height="{Binding ThumbnailSize}"
Margin="10" />
Code Listing 1 - Single Use
This works great, and easily binds to ThumbnailSize on my ViewModel so I can change the size of the image in the control however I want.
The problem is when I want to expand the use of this control into a list, I'm running into a few problems.
To begin, I've styled the ListBox control to meet my needs like so...
<Style TargetType="{x:Type ListBox}"
x:Key="WrappingImageListBox">
<!-- Set the ItemTemplate of the ListBox to a DataTemplate
which explains how to display an object of type BitmapImage. -->
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<tcb:ThumbnailCheckBox ImagePath="{Binding ImagePath}"
IsChecked="{Binding Selected}"
Height="{TemplateBinding utilities:MyAttachedProperties.ImageSize}"
CornerRadius="8"
Margin="10">
</tcb:ThumbnailCheckBox>
</DataTemplate>
</Setter.Value>
</Setter>
<!-- Swap out the default items panel with a WrapPanel so that
the images will be arranged with a different layout. -->
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
<!-- Set this attached property to 'Disabled' so that the
ScrollViewer in the ListBox will never show a horizontal
scrollbar, and the WrapPanel it contains will be constrained
to the width of the ScrollViewer's viewable surface. -->
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility"
Value="Disabled" />
</Style>
Code Listing 2 - ListBox Style
And I call it like this from my main view...
<ListBox ItemsSource="{Binding DirectoryPictures}"
Grid.Row="1"
Style="{DynamicResource WrappingImageListBox}"
Background="Transparent"
util:MyAttachedProperties.ImageSize="500"/>
Code Listing 3 - Main Call
This works exactly as I'd like, except for the ImageSize property. Both ImagePath and Selected are properties of the individual list items being bound to the ListBox.
As you can see, I created an attached property to try to pass the value (500), but it doesn't seem to be working. I should note that I think the style I've created is correct because the elements use the default value.
public static class MyAttachedProperties
{
public static double GetImageSize(DependencyObject obj)
{
return (double)obj.GetValue(ImageSizeProperty);
}
public static void SetImageSize(DependencyObject obj, double value)
{
obj.SetValue(ImageSizeProperty, value);
}
public static readonly DependencyProperty ImageSizeProperty =
DependencyProperty.RegisterAttached(
"ImageSize",
typeof(double),
typeof(MyAttachedProperties),
new FrameworkPropertyMetadata(50D));
}
Code Listing 4 - Attached Property
The 50D specified on the last line is applying to the listed control. If I change it, and recompile, the end result changes. But the sent value of 500 I specified in my ListBox Main call (listing 3) is not ever sent. Of course, I would eventually like to change the 500 into a bound property on my view model, but I won't do that until I get it working with an explicit value.
Can someone help me figure out how to send a value from my main ListBox call (listing 3) and apply it to the individual items that are populated by the template? The other properties I have work, but they are a properties of each item in the List I'm binding to the ListBox, whereas ImageSize is not.
EDIT To address First Response
This seems to be working, but it's kind of peculiar. My listbox is now being called like so...
<ListBox ItemsSource="{Binding DirectoryPictures}"
Grid.Row="1"
Style="{DynamicResource WrappingImageListBox}"
Background="Transparent" />
And I've changed my style to the code you suggested...
<tcb:ThumbnailCheckBox ImagePath="{Binding ImagePath}"
IsChecked="{Binding Selected}"
Height="{Binding Path=DataContext.ThumbnailSize, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListBox}}}"
CornerRadius="8"
Margin="10">
My only concern is, now the style is accessing the ViewModel for that control directly rather than receiving a bound value.
Suppose I wanted to use the ListBox again, but on another UserControl whose ViewModel didn't have ThumbnailSize property, but used one by another name?
You see where I'm going with this... the current solution is not very extensible and is limited to the current classes as they are named exactly.
In fact, in a perfect world, I'd like to have variable names for the ImagePath and Selected properties, but that's a different discussion.
It's possible to use FindAncestor. The idea of that is, child traverses through logical tree, and tries to find parent with concrete type (in this case, ListBox), and then accesses attached property. See http://wpftutorial.net/BindingExpressions.html for more binding expressions.
In your ItemTemplate, this is how you could access ThumbnailSize property:
{Binding Path=(util:MyAttachedProperties.ImageSize),
RelativeSource={RelativeSource
Mode=FindAncestor,
AncestorType={x:Type ListBox}}}
Essentially, the question asked here was a little bit opposite, but results are same. "How could items in ListBox access ListBox (attached) properties.