WPF Tree in ComboEditor - Infragistics WPF controls - c#

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}).

Related

Grouping ListView by Property of ViewModel in DataTemplate

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>

UWP TreeView ItemTemplateSelector not working

The WPF version of this question is here: But it hasn't been answered and I don't know if the UWP TreeView will have the same answer.
I'm trying to add a DataTemplateSelector to the new UWP TreeViews that were just added to windows 10 version 1803 but it isn't working. It is documented here how to use the XAML TreeView Control and even shows how to modify the template to change the Item Datatemplate which works fine. I need to use a datatemplate selector since each of my nodes is using different objects and I need them displayed differently. The TreeView.Node.Content is being set just fine and everything works except it is passing null over to the datatemplateselector in the Object parameter.
Here is my code: (same as the example from Microsoft just with using ItemTemplateSelector)
<Style TargetType="TreeView">
<Setter Property="IsTabStop" Value="False" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="TreeView">
<TreeViewList x:Name="ListControl"
ItemTemplateSelector="{StaticResource CardSelector}"
ItemContainerStyle="{StaticResource TreeViewItemStyle}"
CanDragItems="True"
AllowDrop="True"
CanReorderItems="True">
<TreeViewList.ItemContainerTransitions>
<TransitionCollection>
<ContentThemeTransition />
<ReorderThemeTransition />
<EntranceThemeTransition IsStaggeringEnabled="False" />
</TransitionCollection>
</TreeViewList.ItemContainerTransitions>
</TreeViewList>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Does anyone have any insight or experience on this? My datatemplateselector "CardSelector" works fine and I have been using it in several places without any trouble.
So the point of my question isn't to get anything that I have working but to see if the TreeViewControl works with a DataTemplateSelector. I only have "CardTemplateSelector" in there because I use it in several other places of my app and I know it works. My question is really a "yes, treeview works with a selector" or "no it doesn't" I'm really looking for someone else to try it with their own test template selector and to let me know if they can get it working. Any specific code from me is not relevant to the question. Just see if you can get it to work with whatever selector you want
Yes. The TreeView work well with ItemTemplateSelector.
I used the all code in the document and create a custom class like the following:
public class Test
{
public string Name { get; set; }
}
I made another DataTemplate like this:
<DataTemplate x:Key="TreeViewObjDataTemplate">
<Grid Height="44">
<TextBlock
Text="{Binding Content.Name}"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Style="{ThemeResource BodyTextBlockStyle}"/>
</Grid>
</DataTemplate>
My CardTemplateSelector class is the following:
public class CardTemplateSelector: DataTemplateSelector
{
public DataTemplate TreeViewItemDataTemplate { get; set; }
public DataTemplate TreeViewObjDataTemplate { get; set; }
protected override DataTemplate SelectTemplateCore(object item)
{
TreeViewNode treeViewNode = item as TreeViewNode;
if (treeViewNode.Content is StorageFolder|| treeViewNode.Content is StorageFile)
{
return TreeViewItemDataTemplate;
}
if (treeViewNode.Content is Test)
{
return TreeViewObjDataTemplate;
}
return base.SelectTemplateCore(item);
}
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
{
return SelectTemplateCore(item);
}
}
I just add the new lines code in MainPage.xaml.cs:
TreeViewNode objnode = new TreeViewNode();
Test test = new Test() {Name="Parent"};
objnode.Content = test;
objnode.IsExpanded = true;
objnode.HasUnrealizedChildren = true;
sampleTreeView.RootNodes.Add(objnode);
The following is the whole xaml page resource code:
<Page.Resources>
<DataTemplate x:Key="TreeViewItemDataTemplate">
<Grid Height="44">
<TextBlock
Text="{Binding Content.DisplayName}"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Style="{ThemeResource BodyTextBlockStyle}"/>
</Grid>
</DataTemplate>
<DataTemplate x:Key="TreeViewObjDataTemplate">
<Grid Height="44">
<TextBlock
Text="{Binding Content.Name}"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Style="{ThemeResource BodyTextBlockStyle}"/>
</Grid>
</DataTemplate>
<local:CardTemplateSelector x:Name="CardTemplateSelector" TreeViewItemDataTemplate="{StaticResource TreeViewItemDataTemplate}" TreeViewObjDataTemplate="{StaticResource TreeViewObjDataTemplate}"></local:CardTemplateSelector>
<Style TargetType="TreeView">
<Setter Property="IsTabStop" Value="False" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="TreeView">
<TreeViewList x:Name="ListControl"
ItemTemplateSelector="{StaticResource CardTemplateSelector}"
ItemContainerStyle="{StaticResource TreeViewItemStyle}"
CanDragItems="True"
AllowDrop="True"
CanReorderItems="True">
<TreeViewList.ItemContainerTransitions>
<TransitionCollection>
<ContentThemeTransition />
<ReorderThemeTransition />
<EntranceThemeTransition IsStaggeringEnabled="False" />
</TransitionCollection>
</TreeViewList.ItemContainerTransitions>
</TreeViewList>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Page.Resources>
So far I have answered your question. But I still want to let you know How to ask a good question. In my above comments, I asked you to provide the relevant code, then I could quickly reproduce your question and help you diagnose it. But you said I'm really looking for someone else to try it with their own test template selector and to let me know if they can get it working.. It's Ok. You could see that only I replied. You asked this question for many days. No other community members helped you on this question. That's why I ask you to post some code here. If you provide the relevant code here, I believe many community members will be glad to help you on this question. I really hope you could understand it.
There seems to be confusion about where to apply the DataTemplate. And the all important TargetType is ignored.
If you want a handle on the data item in your custom DataTemplateSelector, you need to:
OPTION 1
Apply the DataTemplateSelector on TreeView.ItemTemplateSelector
Make sure that the DataTemplates have TreeViewNode as the target type.
Only then the data item of the TreeViewNode is supplied to the SetTemplateCore(object item) and SetTemplateCore(object item, DependencyObject container) overrides of your custom DataTemplateSelector.
A working example is found here: Pictures and Music library tree view
OPTION 2
Apply the DataTemplateSelector on TreeViewItem.ContentTemplateSelector
Make sure that the DataTemplates have [YOUR-DATA-TYPE] as the target type
In the TreeView.ItemTemplate bind the DataContext AND Content property to [YOUR-DATA-TYPE], i.e.
<TreeView.ItemTemplate>
<DataTemplate x:DataType="[YOUR-DATA-TYPE]">
<TreeViewItem DataContext="{Binding}" ... Content="{Binding}">
<TreeViewItem.ContentTemplateSelector>
<YourDataTemplateSelector.TemplateA>
<DataTemplate x:DataType="[YOUR-DATA-TYPE]">
...
// YourDataTemplateSelector
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
{
if (item == null) return null;
return (([YOUR-DATA-TYPE])item).IsSomething ? TemplateA : TemplateB;
}

C# WPF Context Menu Data Binding

I'm trying to create a dynamic Context Menu in the WPF DataGrid. The following are the issues that I need help:
1) Root Menu Item Header are not bind with ViewModel while the submenu works fine.
2) The submenu always pop up on the left side instead of the right. How can I fix this with style?
<DataGrid.ContextMenu>
<ContextMenu ItemsSource="{Binding PackageCM.Members}" HasDropShadow="True" Placement="Right">
<ContextMenu.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Header" Value="{Binding CategoryName}" />
</Style>
</ContextMenu.ItemContainerStyle>
<ContextMenu.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Items}">
<MenuItem Header="{Binding DisplayName}" Command="{Binding AllPackagesVM.OpenCOBAPackageCommand, Source={StaticResource Locator}}"></MenuItem>
</HierarchicalDataTemplate>
</ContextMenu.ItemTemplate>
</ContextMenu>
Root Menu Item Header are not being bind.
Basically, Context Menu is binding to the PackageCM.Members with has a list of Category object and I want to display the CategoryName on the Context Menu root. Following that, Each Category contains a list of Items which will be showed as submenu.
Thanks in advance for help.
First, your ContextMenu.ItemTemplate definition is incorrect, when you set an ItemSource for ContextMenu you do not define MenuItems yourself because actually ContextMenu will wrap this content inside another MenuItem. So you need to change your template to something like this:
<ContextMenu ItemsSource="{Binding PackageCM.Members}" ...>
<ContextMenu.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Items}">
<TextBlock Text="{Binding DisplayName}"></TextBlock >
</HierarchicalDataTemplate>
</ContextMenu.ItemTemplate>
</ContextMenu>
You need to put a TextBlock instead of MenuItem because you want to display a text in your ContextMenu menus and bind its Text property to a propety in your model. But so this to work, the model used for root menus and model used for sub-menus must have a property that is named equally, in your case it is DisplayName for sub-menus so in your root menus model must also have a property named DisplayName, This property is bound to Text property of the TextBlock.
You need to do some renaming in your models or introduce an new propety named DisplayName in Category model. So your models would have a common propety something like in this snippet:
// for root menu
public class Category
{
public string CategoryName { get; }
public string DisplayName => CategoryName;
...
}
// for submenus
public class Item
{
public string DisplayName { get; }
...
}
Hopefully this explanation helps you understand the problem for missing header values.

Generate a DataGrid from a List using MVVM

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.

How to find a data-binded ListBoxItem position?

I have a ListBox that its ItemsSource is given from a class based on the data binded items template. I want to find ListBox.SelectedItem position relative to the ListBox. Since I've used a class to feed ItemsSource, I'm not be able to cast ListBox.SelectedItem (which has a type of object) to the ListBoxItem. (Instead I should cast it to the source class type.)
What's the way? -Thanks
Details: (Arbitrary)
There is a ListBox which implements a Style like so:
<Style x:Key="MyListBoxStyle" TargetType="{x:Type ListBox}">
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<Border ...>
<StackPanel ...>
<Image Source="{Binding Path=ItemImageSource}" .../>
<TextBlock Text="{Binding Path=ItemTitle}" .../>
</StackPanel>
</Border>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
The ListBox has been used as follows:
<ListBox x:Name="MyListBox"
ItemsSource="{Binding}"
Style="{StaticResource ResourceKey=MyListBoxStyle}"/>
Also there is a class that supports MyListBox data-binding info:
internal class MyListBoxItemBinding
{
public string ItemTitle { get; set; }
public ImageSource ItemImageSource { get; set; }
}
And to feed the MyListBox:
MyListBox.ItemsSource = new List<MyListBoxItemBinding> { /* some items */ };
Now, how can I find MyListBox.SelectedItem location relative to the MyListBox?
Use ItemsControl.ItemContainerGenerator to get a reference to the item container generator for your ListBox (this is the object that creates wrappers for all your databound objects).
Then, use the ItemContainerGenerator.ContainerFromItem method to get a reference to the UIElement that represents the selected ListBoxItem.
Finally, see the answer to this question to for a way of getting the coordinates of the selected item relative to the ListBox.

Categories