I have a ListView Control bound to a ListCollectionView in a ViewModel.
I wanted to try to group these items but having some problems.
I set the Property grouping in the VM to begin with and then added a GroupStyle.
C#:
ListCollectionView.GroupDescriptions.Add(new PropertyGroupDescription("Category"));
XAML:
<ListView.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Name}"/>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</ListView.GroupStyle>
However the list is now just the category names, no way to see the items themselves.
I'm not really understanding completely what is going on here. When I create a Template for the GroupStyle what am I really binding to? Are there other properties besides Name ?
I just added the GroupStyle to a ListView I has already created where I for example included a ItemTemplate. Is that something that is messing with the GroupStyle?
What if the Items in the list belong to another class and I wan't to group based on what instance of class they belong to (it has an ID). I would then have the group name as a property on this parent class. Is that possible?
PARTIAL SOLUTION:
Problem was with the style applied on the ListView. I have no idea what about the style was interefering.
FULL SOLUTION
I wasn't using a ItemsPresenter in my listbox ControlTemplate opting to use a Panel with IsItemsHost set to true. It seems ItemsPresenter must be used for GroupStyling to work correctly.
I think the error lies elsewhere in your code.
Usually, you expose a collection of Models on your ViewModel
namespace Derp
{
public sealed class ViewModel
{
public ObservableCollection<Model> Items {get;set;}
// initialization code not shown
}
public sealed class Model
{
public string GroupName {get;set;}
public string ModelName {get;set;}
}
}
In your View, you bind a CollectionViewSource to this collection:
<Window.DataContext>
<ViewModel xmlns="clr-namespace:Derp" />
</Window.DataContext>
<Window.Resources>
<CollectionViewSource
Source="{Binding Items}"
x:Key="GroupedItems">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription
PropertyName="GroupName" />
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
</Window.Resources>
Next, we bind our list control to this CollectionViewSource (using a combo in this example):
<ComboBox
ItemsSource="{Binding Source={StaticResource GroupedItems}}"
DisplayMemberPath="ModelName">
<ComboBox.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<TextBlock
Text="{Binding Name}" />
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</ComboBox.GroupStyle>
</ComboBox>
Where it can get confusing is that, within the GroupStyle, you aren't binding against your Model, you are binding against a collection of Models which is grouped on (in this case) the property "GroupName". The CollectionViewSource groups your Models into collections that extend CollectionViewGroup. These groups have a property called Name, which contains the common value on which your Models are grouped (the value of the GroupName property). So, in the HeaderTemplate, you are binding to CollectionViewGroup.Name.
Related
I have a TabControl with a single specific tab and a collection bound to a collection of VMs, using a different user control. To do this I use a CompositeCollection and DataTemplates defined in the control's resources, selecting correct user control based on the VM type (acting as ContentTemplate).
I also set an ItemTemplate to define the tab item's name with binding, but it's not defined in the resource as I guess would conflict with the "ContentTemplate" ones.
It works fine, but I see the following error traced:
System.Windows.Data Error: 26 : ItemTemplate and ItemTemplateSelector are ignored for items already of the ItemsControl's container type; Type='TabItem'
It looks like there's some conflict between ContentTemplate and ItemTemplate, but I don't know how to fix it?
Code is the following:
<TabControl HorizontalAlignment="Left" Height="300" Width="500">
<TabControl.Resources>
<CollectionViewSource x:Key="personCollection" Source="{Binding Persons}" />
<DataTemplate DataType="{x:Type viewModel:Main}">
<local:MainView />
</DataTemplate>
<DataTemplate DataType="{x:Type viewModel:Person}">
<local:PersonView />
</DataTemplate>
</TabControl.Resources>
<TabControl.ItemsSource>
<CompositeCollection>
<TabItem Header="General" Content="{Binding }"/>
<CollectionContainer Collection="{Binding Source={StaticResource personCollection}}" />
</CompositeCollection>
</TabControl.ItemsSource>
<TabControl.ItemTemplate>
<DataTemplate DataType="viewModel:Person">
<TextBlock Text="{Binding FirstName}" />
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>
The error you observe is pretty obvious.
You define the ItemsSource of your TabControl as a CompositeCollection that contains elements of different types:
a TabItem "General";
a bunch of Person viewmodels.
So you're just mixing in one collection a view and some viewmodels - that's not neat. WPF informs you about this with the error message. The engine tries to create views (using DataTemplates) for the items and suddenly encounters an already specified view (a TabItem) that is exactly of type of the item container (because for the TabControl, a view for each viewmodel will be inserted in a TabItem container). So WPF simply inserts the TabItem into the TabControl and notifies that it has not used any ItemTemplate or ItemTemplateSelector for creating it.
You could simply ignore this error, because in the end the control should look like you want it to (I suppose).
An alternative (and probably neater) way is not to mix views and viewmodels in one collection, but rather specify a "general" viewmodel for the "General" tab:
<TabControl.ItemsSource>
<CompositeCollection>
<viewModel:GeneralViewModel/>
<CollectionContainer Collection="{Binding Source={StaticResource personCollection}}" />
</CompositeCollection>
</TabControl.ItemsSource>
And of course you then need to tell WPF how to visualize it:
<TabControl.Resources>
<DataTemplate DataType="{x:Type viewModel:GeneralViewModel}">
<local:GeneralView />
</DataTemplate>
<!-- ... -->
</TabControl.Resources>
Update
To address the issues in your comments.
1.
How do I bind the GeneralViewModel to the one that exist in my DataContext?
This is possible, but with some overhead. You have to create a binding proxy for this. (Take a look here.)
The second thing you will need is a markup extension:
class BindingProxyValue : MarkupExtension
{
public BindingProxy Proxy { get; set; }
public override object ProvideValue(IServiceProvider serviceProvider)
{
return Proxy.DataContext;
}
}
Use this markup extension together with the binding proxy in your collection:
<TabControl.Resources>
<local:BindingProxy x:Key="Proxy" DataContext="{Binding GeneralViewModel}"/>
</TabControl.Resources>
<!--...-->
<CompositeCollection>
<local:BindingProxyValue Proxy="{StaticResource Proxy}"/>
<CollectionContainer Collection="{Binding Source={StaticResource personCollection}}" />
</CompositeCollection>
You can extend the markup extension as you like, e.g. in such a way that it can observe the object updates and replace the item in the target CompositeCollection.
2.
How do I specify general tab's header name?
You can use ItemTemplates, but it becomes a little bit complicated. You have to implement a DataTemplateSelector for your TabControl:
class YourTabItemDataTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
FrameworkElement element = container as FrameworkElement;
if (element != null && item != null)
{
if (item is GeneralViewmodel)
{
return (DataTemplate)element.FindResource("GeneralTabItemTemplate");
}
else
{
return (DataTemplate)element.FindResource("PersonTabItemTemplate");
}
}
return null;
}
}
Then you can define the different ItemTemplates for different TabItems:
<TabControl.Resources>
<!-- ... -->
<DataTemplate x:Key="GeneralTabItemTemplate">
<TextBlock Text="General" />
</DataTemplate>
<DataTemplate x:Key="PersonTabItemTemplate">
<TextBlock Text="{Binding FirstName}" />
</DataTemplate>
</TabControl.Resources>
The question is: is this effort worth it or are you okay with that error message 26? You decide.
http://www.filedropper.com/wpfapplication9
that link has a project in VS2013 with a sample issue.
my problem is, how to set DataTemplate to UserControl, in ItemsControl.
<ItemsControl ItemsSource="{Binding Collection}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Title}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
my Collections is a
public IEnumerable<MyUC> Collection {get;}
my MyUC is a
public partial class MyUC : UserControl
{
public string Title { get; set; }
}
When i try to show that collection im getting
MyUC.Content
insted
MyUC.Title
when i change ItemsSouce to ListBox, datatemplate starts working.
but i need to show collection without ListBox addons.
If I understand you correctly, you just want to display the Title property value of your UserControls from your collection. All that you have to do is to declare an appropriate DataTemplate for them in the Resources section:
<DataTemplate DataType="x:Type={YourPrefix:MyUC}">
<TextBlock Text="{Binding Title}"/>
</DataTemplate>
To be honest though, you seem to be going about this in the wrong way. In WPF, we generally don't put UI elements into collections, instead preferring to work with custom data classes that have DataTempates to define what they should look like. In your case, it would look something like this:
<DataTemplate x:Key="TitleTemplate" DataType="x:Type={YourPrefix:MyDataClass}">
<TextBlock Text="{Binding Title}"/>
</DataTemplate>
<DataTemplate x:Key="ControlTemplate" DataType="x:Type={YourPrefix:MyDataClass}">
<YourControlPrefix:MyUC />
</DataTemplate>
You'd then use the TitleTemplate when you want to see just the Title property values and the ControlTemplate when you want to see the whole UserControl.
I'm a newcomer to Caliburn.Micro and there are a few things I'm still not getting.
ViewModel first:
First of is a ViewModel that manages a Collection of other ViewModels:
public class NavigationBarViewModel : PropertyChangedBase
{
public BindableCollection<IHaveDisplayName> Items { get; set; }
}
I've got a ItemsControl (it's Telerik RadOutlookBar if that matters) as the root of a UserControl
of that view and I set the ItemTemplate too ensure that the ViewModels I insert into the collection are wrapped in a corresponding RadOutlookBarItem ( should I use ItemContainer instead of ItemTemplate here? ).
<telerik:RadOutlookBar x:Name="Items">
<telerik:RadOutlookBar.TitleTemplate>
<DataTemplate>
<ContentControl Content="{Binding Path=DisplayName}" />
</DataTemplate>
</telerik:RadOutlookBar.TitleTemplate>
<telerik:RadOutlookBar.ItemTemplate>
<DataTemplate>
<telerik:RadOutlookBarItem cal:Bind.Model="{Binding}"
Header="{Binding Path=DisplayName}">
<ContentControl />
</telerik:RadOutlookBarItem>
</DataTemplate>
</telerik:RadOutlookBar.ItemTemplate>
</telerik:RadOutlookBar>
This way I wan't the ViewModels in the collection to appear where the ContentControl is. I Bind the model to the root item of the DataTemplate to ensure conventions will work but have no idea how to bind to the ContentControl with convention. The DataContext inside the DataTemplate is of course the ViewModel itself. Using normal WPF standard I would put Content="{Binding}".
Now the model is there inside the RadOutlookBarItem but it's view doesn't get applied. Not even View can't be found, only a string with the class name.
Isn't this the proper way to do this?
As I answered here: Dynamic Telerik RadOutlookBar headers come out wrong with ItemTemplate in which I thought was a unrelated matter I was using the wrong property. ItemTemplate controls the picker and contentTemplate what comes up when you select. Here is the code that works:
<telerik:RadOutlookBar x:Name="Items">
<telerik:RadOutlookBar.ContentTemplate>
<DataTemplate >
<ContentControl cal:View.Model="{Binding}" />
</DataTemplate>
</telerik:RadOutlookBar.ContentTemplate>
<telerik:RadOutlookBar.TitleTemplate>
<DataTemplate>
<TextBlock x:Name="DisplayName"
cal:Bind.Model="{Binding}" />
</DataTemplate>
</telerik:RadOutlookBar.TitleTemplate>
<telerik:RadOutlookBar.ItemTemplate>
<DataTemplate>
<TextBlock x:Name="DisplayName"
cal:Bind.Model="{Binding}" />
</DataTemplate>
</telerik:RadOutlookBar.ItemTemplate>
</telerik:RadOutlookBar>
I am DataTemplating a listbox's ItemSource to display a series of comboboxes. I want to give the DisplayMemberPath of the combo to a property, which is in a different source than its own ItemsSource. (Assuming DisplayMemberPath is just a string representing name of a property, I am getting this from the user). I have achieved this with a CollectionViewSource, but all the comboboxes are displaying the same list.
What I am expecting to have after data templating is to have comboboxes display,
ComboboxInstance1.DisplayMemberPath = PropertyMapOfEmployee in FilterControls[0]
ComboboxInstance2.DisplayMemberPath = PropertyMapOfEmployee in FilterControls[1]
Is this possible to achieve in XAML ?
Thanks. Mani
UserControl:
<Resources>
<CollectionViewSource x:Key="bindingSource" Source="{Binding BindingItems}"/>
<CollectionViewSource x:Key="FilterSource" Source="{Binding FilterControls}"/>
<DataTemplate DataType="{x:Type CustomTypes:FilterElement}">
<ComboBox ItemsSource="{Binding Source={StaticResource bindingEmp}"
DisplayMemberPath="{Binding Source={StaticResource FilterSource},
Path=PropertyMapofEmployee}" />
</DataTemplate>
<Resources>
---
<DockPanel>
<ListBox x:Name="lstBox" ItemsSource="{Binding FilterControls}" />
</DockPanel>
ViewModel:
List<FilterElement> FilterControls;
List<Employee> Employees
class FilterElement
{
string Caption;
String PropertyMapofEmployee
}
<ComboBox ItemsSource="{Binding Source={StaticResource bindingEmp}"
DisplayMemberPath="{Binding PropertyMapofEmployee}" />
I'm not sure you can do that in XAML. (Having the DisplayMemberPath point to a property that is on an object other than the DataContext). You may want to look at the RelativeSource Class to see if that would meet your needs.
Have you thought about providing a reference in your Employee object to the FilterElement and then hooking up to the binding the Employee.PropertyMapOfEmployee property that you've created?
Suppose I have a dataset with those two immortal tables: Employee & Order
Emp -> ID, Name
Ord -> Something, Anotherthing, EmpID
And relation Rel: Ord (EmpID) -> Emp (ID)
It works great in standard master/detail scenario
(show employees, follow down the relation, show related orders),
but what when I wan't to go the opposite way (show Ord table with Emp.Name)?
Something like this:
<stackpanel> // with datacontext set from code to dataset.tables["ord"]
<TextBox Text="{Binding Something}"/>
<TextBox Text="{Binding Anotherthing}"/>
<TextBox Text="{Binding ???}"/> // that's my problem, how to show related Emp.Name
</stackpanel>
Any ideas? I can create value converter, but if I wan't to use dataset instance which I get from parent module it gets tricky.
If you want to synchronize the contents of multiple controls, you will need to have them share the same binding source through the DataContext set on a common parent control. Here is an example:
<StackPanel>
<StackPanel.Resources>
<ObjectDataProvider x:Key="ds" ObjectType="{x:Type mynamespace:MyDataSet}" />
</StackPanel.Resources>
<!-- We set the data context to the collection of rows in the table -->
<StackPanel DataContext="{Binding Source={StaticResource ds}, Path=USERS.Rows}">
<ListBox ItemsSource="{Binding}"
DisplayMemberPath="NAME"
IsSynchronizedWithCurrentItem="True" />
<TextBox Text="{Binding Path=NAME}"/>
<TextBox Text="{Binding Path=COUNTRIESRow.NAME}"/>
</StackPanel>
</StackPanel>
Setting the IsSynchronizedWithCurrentItem property to 'True' will cause the ListBox.SelectedItem property to be automatically synchronized with the CollectionView.CurrentItem of the binding source, that is the collection of rows set at the DataContext. This means that the currently selected row in the ListBox becomes the binding source for the two TextBox controls.
Assuming that you are using a strongly-typed DataSet, in order to bind the TextBox to the 'EmpRow.Name' property, you will probably have to expose it as a property on the 'OrdDataTable' class.
Since Visual Studio generates the typed DataSet code with partial classes, you could add the property to the 'OrdDataTable' class this way:
using System.Data;
public partial class OrdDataTable : DataTable
{
public string EmpName
{
get { return this.EmpRow.Name; }
}
}
Then you would be able to bind to the 'EmpName' property of the 'OrdDataTable' object in the data context.
What is the DataContext for the two TextBox controls?For the second binding to work the DataContext must be set to an instance of the 'USERSDataTable'. Since these are contained in an array in the DataSet, you have to explicitly tell which table you want to bind to. Something like:
<StackPanel>
<StackPanel.Resources>
<ObjectDataProvider x:Key="ds" ObjectType="{x:Type mynamespace:MyDataSet}" />
</StackPanel.Resources>
<!-- Notice we set the data context to the first item in the array of tables -->
<StackPanel DataContext="{Binding Source={StaticResource ds}, Path=USERS[0]}">
<TextBox Text="{Binding NAME}"/>
<TextBox Text="{Binding COUNTRIESRow.NAME}"/>
</StackPanel>
</StackPanel>