I am trying to create a UserControl that contains an ItemsControl which should display Parameters and their values. The values must be editable and these values should be transferred back to the ViewModel.
It should be possible to define which property represents the parameter name and which property the parameter value.
Parameter class:
public class Parameter
{
public string Name { get; set; }
public string Value { get; set; }
}
ViewModel:
public class MyViewModel : INotifyPropertyChanged
{
...
public ObservableCollection<Parameter> Parameters { get; set; }
...
}
UserControl ("ParameterList.xaml"):
<UserControl x:Name="ParameterList" ...>
<Border BorderBrush="Black" BorderThickness="1" Height="100">
<!-- I don't know if this binding expression is correct -->
<ItemsControl ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type controls:ParameterList}}, Path=Parameters}">
<ItemsControl.ItemTemplate>
<Border>
<!-- The property path defined via "ParameterNameMember" should be bound. -->
<TextBlock Text="{Binding ???}" />
<!-- The property path defined via "ParameterValueMember" should be bound. -->
<!-- The value edited in this TextBox should be transferred to the ViewModel. -->
<TextBox Text="{Binding ???}" />
</Border>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Border>
</UserControl>
UserControl code behind:
public partial class ParameterList : UserControl
{
public IEnumerable Parameters
{
get => (IEnumerable)GetValue(ParametersProperty);
set => SetValue(ParametersProperty, value);
}
public string ParameterNameMember
{
get => (string)GetValue(ParameterNameMemberProperty);
set => SetValue(ParameterNameMemberProperty, value);
}
public string ParameterValueMember
{
get => (string)GetValue(ParameterValueMemberProperty);
set => SetValue(ParameterValueMemberProperty, value);
}
public static readonly DependencyProperty ParametersProperty =
DependencyProperty.Register("Parameters", typeof(object),
typeof(ParameterList), new PropertyMetadata(default(IEnumerable)));
public static readonly DependencyProperty ParameterNameMemberProperty =
DependencyProperty.Register("ParameterNameMember", typeof(string),
typeof(ParameterList), new PropertyMetadata(""));
public static readonly DependencyProperty ParameterValueMemberProperty =
DependencyProperty.Register("ParameterValueMember", typeof(string),
typeof(ParameterList), new PropertyMetadata(""));
public ParameterList()
{
InitializeComponent();
}
}
I want to use the control as follows:
<uc:ParameterList
x:Name="ParameterList"
Parameters="{Binding Parameters}"
ParameterNameMember="Name"
ParameterValueMember="Value" />
Since I don't have that much experience with WPF, I need some help with the data binding. I would be very grateful if I could get some useful suggestions.
Related
I am trying to use a UserControl inside an ItemsRepeater and having issues with it.I am using Prism and the MVVM model
I defined a UserControl in a separate A.xaml file
<UserControl>
<Grid>
<Button Background="Grey" Content="{x:Bind AViewModel.text}">
<Button>
<Grid>
<UserControl>
The corresponding A.xaml.cs file has the binding to AViewModel which defines the property text
I have another XAML file B.xaml which uses this control defined as follows
<Grid>
<ItemsRepeater ItemSource={"x:Bind BViewModel.ListOfObservableCollection"}>
<ItemRepeater.Layout>
<StackLayout>
</StackLayout>
</ItemRepeater.Layout>
</ItemsRepeater>
</Grid>
This XAML file has a corresponding B.XAML.cs file which binds to BViewModel which has the List of Observable collection. I wish to display a vertical list of button which has text from the List of Observation Collection by using UserControl. How do i achieve this ?
How to use UserControls in an ItemsRepeater for UWP
You could insert UserControl into ItemsRepeater's ItemTemplate. And give UserControl DependencyProperty to receive text value. Please note you need edit ListOfObservableCollection content to AViewModel like the following
UserControl
<UserControl>
<Grid>
<Button Background="Gray" Content="{Binding ButtonContent}" />
</Grid>
</UserControl>
code behind
public sealed partial class AControl : UserControl
{
public AControl()
{
this.InitializeComponent();
(this.Content as FrameworkElement).DataContext = this;
}
public string ButtonContent
{
get { return (string)GetValue(ButtonContentProperty); }
set { SetValue(ButtonContentProperty, value); }
}
// Using a DependencyProperty as the backing store for ButtonContent. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ButtonContentProperty =
DependencyProperty.Register("ButtonContent", typeof(string), typeof(AControl), new PropertyMetadata(null));
}
Usage
<Page.DataContext>
<local:BViewModel x:Name="ViewModel" />
</Page.DataContext>
<Grid>
<muxc:ItemsRepeater ItemsSource="{Binding ListOfObservableCollection}">
<muxc:ItemsRepeater.ItemTemplate>
<DataTemplate>
<local:AControl ButtonContent="{Binding Text}" />
</DataTemplate>
</muxc:ItemsRepeater.ItemTemplate>
</muxc:ItemsRepeater>
<local:AControl ButtonContent="hh" />
</Grid>
Code behind
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
}
}
public class AViewModel
{
public string Text { get; set; }
}
public class BViewModel
{
public ObservableCollection<AViewModel> ListOfObservableCollection { get; set; }
public BViewModel()
{
ListOfObservableCollection = new ObservableCollection<AViewModel>();
ListOfObservableCollection.Add(new AViewModel { Text = "Test1" });
ListOfObservableCollection.Add(new AViewModel { Text = "Test1" });
ListOfObservableCollection.Add(new AViewModel { Text = "Test1" });
ListOfObservableCollection.Add(new AViewModel { Text = "Test1" });
}
}
I have this DependencyProperty
public ObservableCollection<DataTemplate> WizardTemplateCollection
{
get { return (ObservableCollection<DataTemplate>)GetValue(WizardTemplateCollectionProperty); }
set { SetValue(WizardTemplateCollectionProperty, value); }
}
// Using a DependencyProperty as the backing store for MyProperty. This enables animation, styling, binding, etc...
public static readonly DependencyProperty WizardTemplateCollectionProperty =
DependencyProperty.Register("WizardTemplateCollection", typeof(ObservableCollection<DataTemplate>), typeof(CustomWizardControl), new PropertyMetadata(new ObservableCollection<DataTemplate>()));
And want to do this:
<custom:CustomWizardControl>
<custom:CustomWizardControl.WizardTemplateCollection>
<DataTemplate>
<Rectangle></Rectangle>
</DataTemplate>
<DataTemplate>
<Rectangle></Rectangle>
</DataTemplate>
<DataTemplate>
<Rectangle></Rectangle>
</DataTemplate>
</custom:CustomWizardControl.WizardTemplateCollection>
</custom:CustomWizardControl>
What DataType do i need? Or how can i initialize a ObservableCollection in XAML.
Additional:
public class CustomWizardControl : Control {}
Your CustomWizardControl class must inherit from DepenedencyObject or one of its derived types like for example UIElement or Control:
public class CustomWizardControl : Control
{
public ObservableCollection<DataTemplate> WizardTemplateCollection
{
get { return (ObservableCollection<DataTemplate>)GetValue(WizardTemplateCollectionProperty); }
set { SetValue(WizardTemplateCollectionProperty, value); }
}
...
}
This works:
public class CustomWizardControl : Control
{
public CustomWizardControl()
{
WizardTemplateCollection = new ObservableCollection<DataTemplate>();
}
public ObservableCollection<DataTemplate> WizardTemplateCollection
{
get { return (ObservableCollection<DataTemplate>)GetValue(WizardTemplateCollectionProperty); }
set { SetValue(WizardTemplateCollectionProperty, value); }
}
public static readonly DependencyProperty WizardTemplateCollectionProperty =
DependencyProperty.Register("WizardTemplateCollection", typeof(ObservableCollection<DataTemplate>), typeof(CustomWizardControl), new PropertyMetadata(null));
}
<local:CustomWizardControl x:Name="ctrl">
<local:CustomWizardControl.WizardTemplateCollection>
<DataTemplate>
<Rectangle></Rectangle>
</DataTemplate>
<DataTemplate>
<Rectangle></Rectangle>
</DataTemplate>
<DataTemplate>
<Rectangle></Rectangle>
</DataTemplate>
</local:CustomWizardControl.WizardTemplateCollection>
</local:CustomWizardControl>
<TextBlock Text="{Binding WizardTemplateCollection.Count, ElementName=ctrl}" />
You cannot set the generic parameter of ObservableCollection<T> directly in XAML.
Instead you should create your custom DataTemplateCollection inherited from ObservableCollection<DataTemplate>. Then you will be able to use your collection as usual.
public class DataTemplateCollection : ObservableCollection<DataTemplate>
{
}
<custom:CustomWizardControl>
<custom:CustomWizardControl.WizardTemplateCollection>
<custom:DataTemplateCollection>
<DataTemplate>
<Rectangle></Rectangle>
</DataTemplate>
<DataTemplate>
<Rectangle></Rectangle>
</DataTemplate>
<DataTemplate>
<Rectangle></Rectangle>
</DataTemplate>
</custom:DataTemplateCollection>
</custom:CustomWizardControl.WizardTemplateCollection>
</custom:CustomWizardControl>
Additional note: NEVER initialize the default value of dependency properties with mutable objects, because this single mutable instance will be used by every control instance. Instead you must set the default value to null and assing the initial value in the constructor.
First of all, I am a WPF beginner! My approach is potentially not the right way to do what I want so do not hesitate to tell me if that is the case. What I want to do is a composite user control in WPF, using MVVM.
Some classes will do a better presentation than I, here are my view models:
interface IParameter : INotifyPropertyChanged
{
string Name { get; set;}
string Value { get; set;}
}
class TextParameter : ViewModelBase, IParameter
{
private string _value;
public string Name { get; private set; }
public string Value
{
get { return _value; }
set
{
_value = value;
RaisePropertyChanged();
}
}
public TextParameter (string name)
{
this.Name = name;
}
}
class ParameterList : ViewModelBase, IParameter
{
private string _value;
public string Name { get; private set; }
public string Value
{
get { return _value; }
set
{
_value = value;
RaisePropertyChanged();
}
}
ObservableCollection<IParameter> Parameters { get; set; }
public ParameterList (string name, IEnumerable<IParameter> parameters = null)
{
this.Name = name;
this.Parameters = new ObservableCollection<IParameter>(parameters ?? new List<IParameter>());
}
}
I am using MVVM Light, so all the PropertyChanged stuff is managed into ViewModelBase. Also, this is not an exhaustive list of all the parameters, there is some others, more complex but the issue is about these ones.
Here are my custom user controls:
TextParameterControl.xaml:
<UserControl x:Class="Stuff.TextParameterControl" [..] x:Name="parent">
<StackPanel DataContext="{Binding ElementName=parent}" Orientation="Horizontal">
<TextBlock Text="{Binding Path=ParamName, StringFormat='{}{0}:'}" Width="100"></TextBlock>
<TextBox Text="{Binding Path=Value}" Width="100"></TextBox>
</StackPanel>
</UserControl>
TextParameterControl.xaml.cs :
public class TextParameterControl : UserControl
{
#region param name
public string ParamName
{
get { return (string)GetValue(ParamNameProperty); }
set { SetValue(ParamNameProperty, value); }
}
// Using a DependencyProperty as the backing store for ParamName. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ParamNameProperty =
DependencyProperty.Register("ParamName", typeof(string), typeof(TextParameterControl), new PropertyMetadata(String.Empty));
#endregion
#region value
public string Value
{
get { return (string)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
// Using a DependencyProperty as the backing store for Value. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(string), typeof(TextParameterControl), new PropertyMetadata(String.Empty));
#endregion
public TextParameterControl()
{
InitializeComponent();
}
}
ParameterListControl.xaml:
<UserControl x:Class="Stuff.ParameterListControl" [..] x:Name="parent">
<UserControl.Resources>
<DataTemplate x:Key="TextParameterTemplate">
<c:TextParameterControl ParamName="{Binding Name}" Value="{Binding Value}"/>
</DataTemplate>
<DataTemplate x:Key="ParameterListTemplate">
<c:ParameterListControl ParamName="{Binding Name}" Value="{Binding Value}" Items="{Binding Parameters}" />
</DataTemplate>
<s:ParameterTemplateSelector x:Key="ParameterSelector"
TextParameterTemplate="{StaticResource TextParameterTemplate}"
ParameterListTemplate="{StaticResource ParameterListTemplate}"/>
</UserControl.Resources>
<Expander DataContext="{Binding ElementName=parent}" Header="{Binding Path=ParamName}" IsExpanded="True" ExpandDirection="Down">
<StackPanel>
<ItemsControl ItemsSource="{Binding Path=Items}" ItemTemplateSelector="{StaticResource ParameterSelector}"></ItemsControl>
</StackPanel>
</Expander>
</UserControl>
ParameterListControl.xaml.cs:
public partial class ParameterListControl: UserControl
{
#region param name
public string ParamName
{
get { return (string)GetValue(ParamNameProperty); }
set { SetValue(ParamNameProperty, value); }
}
// Using a DependencyProperty as the backing store for ParamName. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ParamNameProperty =
DependencyProperty.Register("ParamName", typeof(string), typeof(ParameterListControl), new PropertyMetadata(String.Empty));
#endregion
#region value
public string Value
{
get { return (string)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
// Using a DependencyProperty as the backing store for Value. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(string), typeof(ParameterListControl), new PropertyMetadata(String.Empty));
#endregion
#region items
public IList<string> Items
{
get { return (List<string>)GetValue(ItemsProperty); }
set { SetValue(ItemsProperty, value); }
}
public static readonly DependencyProperty ItemsProperty =
DependencyProperty.Register("Items", typeof(IList<string>), typeof(ParameterListControl), new PropertyMetadata(new List<string>()));
#endregion
public ParameterListControl()
{
InitializeComponent();
}
}
Here is my custom template selector:
class ParameterTemplateSelector : DataTemplateSelector
{
public DataTemplate ParameterListTemplate { get; set; }
public DataTemplate TextParameterTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
if (item is TextParameter)
{
return this.TextParameterTemplate;
}
else if (item is ParameterList)
{
return this.ParameterListTemplate;
}
throw new Exception(String.Format("This parameter ({0}) is not handled in the application", item.GetType().Name));
}
}
And here is the calling View and ViewModel:
ViewModel:
public class MainViewModel : ViewModelBase
{
public ObservableCollection<IParameter> Parameters { get; set; }
public MainViewModel()
{
this.Parameters = new ObservableCollection<IParameter>();
this.Parameters.Add(new TextParameter("Customer"));
// here I am building my complex composite parameter list
}
View:
<UserControl.Resources>
<DataTemplate x:Key="TextParameterTemplate">
<c:TextParameterControl ParamName="{Binding Name}" Value="{Binding Value}"/>
</DataTemplate>
<DataTemplate x:Key="ParameterListTemplate">
<c:ParameterListControl ParamName="{Binding Name}" Value="{Binding Value}" Items="{Binding Parameters}" />
</DataTemplate>
<s:ParameterTemplateSelector x:Key="ParameterSelector"
TextParameterTemplate="{StaticResource TextParameterTemplate}"
ParameterListTemplate="{StaticResource ParameterListTemplate}"/>
</UserControl.Resources>
<ItemsControl ItemsSource="{Binding Parameters}" ItemTemplateSelector="{StaticResource ParameterSelector}"></ItemsControl>
When I run the application, the TextParameter in the MainViewModel.Parameters are well loaded (VM.Name and VM.Value properties are well binded to UC.ParamName and UC.Value. Contrariwise, the ParameterList in MainViewModel.Parameters are partially loaded. UC.Name is well binded to the UC.ParamName but the VM.Parameters is not binded to the UC.Items (the UC.DataContext is the VM, the VM.Parameters is well defined, but the UC.Items is desperately null).
Do you have any idea of what I am missing ?
(I am not a native speaker, excuse me if my english hurts you)
I see you have a binding MainViewModel.Parameters -> ParameterListControl.Items but you might be missing the binding from ParameterListControl.Items -> ParameterList.Parameters. (That's assuming ParameterList is the ViewModel for the ParameterListControl - you provide the code for DataContext bindings.)
See the accepted answer on this question. (Ignore the comment on Caliburn.Micro - same solution worked for me in MVVM Light.)
Essentially, in the constructor of ParameterListControl you create an extra binding between the dependency property of the view and the viewmodel's property.
(Also, Dbl is right in the comments that when debugging binding problems, the "unimportant" "plumbing" code that you omitted is very important.)
I finally find out:
The Items dependency property of ParameterListControl was a IList<string>. It was a copy/paste mistake from another UC. I changed it to IEnumerable and everything works fine now:
public IEnumerable Items
{
get { return (IEnumerable)GetValue(ItemsProperty); }
set { SetValue(ItemsProperty, value); }
}
public static readonly DependencyProperty ItemsProperty =
DependencyProperty.Register("Items", typeof(IEnumerable), typeof(ParameterListControl), new PropertyMetadata(new List<object>()));
I continued to work on the code and it is now finished and truly composite compared to the sample I posted earlier. If someone is interested in seeing/using this code, you can find it on github.
I am making a user control to represent chosen numbers (like in a lottery). The problem is that when binding to it inside a data template binding does not work.
It works correclty when hardcoding the values.
The errors are of this type and they appear for every dependency property I bind to
Error: BindingExpression path error: 'BackCheckedColor' property not found on 'NumberControlTest.Controls.NumberControl'. BindingExpression: Path='BackCheckedColor' DataItem='NumberControlTest.Controls.NumberControl'; target element is 'NumberControlTest.Controls.NumberControl' (Name='null'); target property is 'CheckedBackgroundColor' (type 'String')
What I find strange is that in this section of the error
BindingExpression: Path='BackCheckedColor' DataItem='NumberControlTest.Controls.NumberControl'
It suggests that it is trying to find the BackCheckedColor in the usercontrol itself. That does not make sense to me. Can somebody help??
User Control Xaml
<UserControl.Resources>
<local:CheckedToBrushConverter x:Key="CheckedToBrushConverter"
CheckedBackgroundColor="{Binding CheckedBackgroundColor}"
CheckedForegroundColor="{Binding CheckedForegroundColor}"
UncheckedBackgroundColor="{Binding UncheckedBackgroundColor}"
UncheckedForegroundColor="{Binding UncheckedForegroundColor}"/>
</UserControl.Resources>
<Grid Tapped="Grid_Tapped">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="16*"/>
<ColumnDefinition Width="130*"/>
<ColumnDefinition Width="16*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="16*"/>
<RowDefinition Height="130*"/>
<RowDefinition Height="30*"/>
</Grid.RowDefinitions>
<Ellipse x:Name="Ellipse" Grid.RowSpan="3" Grid.ColumnSpan="3" Fill="{Binding IsChecked, Converter={StaticResource CheckedToBrushConverter}, ConverterParameter=background}"/>
<Viewbox Grid.Row="1" Grid.Column="1">
<TextBlock x:Name="NumberBlock" TextWrapping="Wrap" FontFamily="Segoe UI" Text="{Binding NumberValue}" Foreground="{Binding IsChecked, Converter={StaticResource CheckedToBrushConverter}, ConverterParameter=foreground}" />
</Viewbox>
</Grid>
User control code behind
public sealed partial class NumberControl : UserControl
{
public NumberControl()
{
this.InitializeComponent();
this.DataContext = this;
}
public string UncheckedBackgroundColor
{
get { return (string)GetValue(UncheckedBackgroundColorProperty); }
set { SetValue(UncheckedBackgroundColorProperty, value); }
}
// Using a DependencyProperty as the backing store for UncheckedBackgroundColor. This enables animation, styling, binding, etc...
public static readonly DependencyProperty UncheckedBackgroundColorProperty =
DependencyProperty.Register("UncheckedBackgroundColor", typeof(string), typeof(NumberControl), new PropertyMetadata(string.Empty));
public string CheckedBackgroundColor
{
get { return (string)GetValue(CheckedBackgroundColorProperty); }
set { SetValue(CheckedBackgroundColorProperty, value); }
}
// Using a DependencyProperty as the backing store for CheckedBackgroundColor. This enables animation, styling, binding, etc...
public static readonly DependencyProperty CheckedBackgroundColorProperty =
DependencyProperty.Register("CheckedBackgroundColor", typeof(string), typeof(NumberControl), new PropertyMetadata(string.Empty));
plus more dependency properties like those.
MainPage xaml
<Page.Resources>
<DataTemplate x:Key="NumberTemplate">
<Grid>
<controls:NumberControl
UncheckedBackgroundColor="{Binding BackUncheckedColor}"
UncheckedForegroundColor="{Binding ForeUncheckedColor}"
CheckedBackgroundColor="{Binding BackCheckedColor}"
CheckedForegroundColor="{Binding ForeCheckedColor}"
NumberValue="{Binding Value}"
IsChecked="{Binding IsChecked}"
HorizontalAlignment="Center"
VerticalAlignment="Center" Width="45" Height="45"/>
</Grid>
</DataTemplate>
</Page.Resources>
<Grid Background="#0f455f">
<GridView x:Name="NumbersGridView" ItemTemplate="{StaticResource NumberTemplate}" ItemsSource="{Binding Numbers, Mode=TwoWay}"/>
<Button x:Name="printButton" Content="Print" VerticalAlignment="Bottom" HorizontalAlignment="Center" Click="printButton_Click"/>
</Grid>
Model class which provides the data of the collection bound to the gridview
public class MockNumber
{
public MockNumber(bool isChecked, int value, string backchcolor, string forchcolor, string backunchcolor, string forunchcolor)
{
IsChecked = isChecked;
Value = value;
BackCheckedColor = backchcolor;
ForeCheckedColor = forchcolor;
BackUncheckedColor = backunchcolor;
ForeUncheckedColor = forunchcolor;
}
public bool IsChecked { get; set; }
public int Value { get; set; }
public string BackCheckedColor { get; set; }
public string ForeCheckedColor { get; set; }
public string BackUncheckedColor { get; set; }
public string ForeUncheckedColor { get; set; }
}
EDIT: How the model is instantiated and bound in the MainPage codebehind.
public MainPage()
{
this.InitializeComponent();
this.DataContext = this;
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
makelist();
}
void makelist()
{
for (int i = 1; i <= 20; i++)
{
Numbers.Add(new MockNumber(i % 4 == 0 ? true : false, i, "#dead2b", "#000000", "#dead2b", "#f0b60c"));
}
}
private ObservableCollection<MockNumber> numbers = new ObservableCollection<MockNumber>();
public ObservableCollection<MockNumber> Numbers
{
get
{
return numbers;
}
set
{
numbers = value;
}
}
The reason why it's trying to find the 'BackCheckedColor' property from the NumberControl is because you set the user control's datacontext to itself.
public NumberControl()
{
this.InitializeComponent();
this.DataContext = this;
}
You're telling the user control that your data context is itself. It means that when you do the "{Binding}" the path should be a property of the user control which I don't think is a good idea.
I understand that you want to bind some dependency properties to your Model class but I didn't see in your example where you instantiated the model class and use it as your data context.
Another thing to consider, you might want to use a custom control instead of a user control. I can see that you added some dependency properties to your user control but in practice, dependency properties added to custom controls and static classes that has attached properties.
EDIT:
After reading your additional code, I can see that the user control's datacontext was being set to 'this' which is itself. You need to remove that.
public sealed partial class NumberControl : UserControl
{
public NumberControl()
{
this.InitializeComponent();
this.DataContext = this; //Remove this line
}
//...
Then after removing that, you usercontrol should inherit the GridViewItem's Binding or you can explicitly put the datacontext in your DataTemplate.
<DataTemplate x:Key="NumberTemplate">
<Grid>
<controls:NumberControl DataContext="{Binding}" <!--specify the data context-->
UncheckedBackgroundColor="{Binding BackUncheckedColor}"
//..
I'm looking for a simple way to sort items of ItemsControl based on a property specified in implicit DataTemplate for the items to which the control is bound. And defining the properties on DataTemplate is crucial here, because I cannot add the sorting property on the item itself.
So, for the below example VM layer:
public interface INamed
{
string Name { get; set; }
}
public class FirstModel : INamed
{
public string Name { get; set; }
}
public class SecondModel : INamed
{
public string Name { get; set; }
}
public class ViewModel
{
public ViewModel()
{
Models = new INamed[] { new SecondModel {Name = "Second"}, new FirstModel {Name = "First"}};
}
public IEnumerable<INamed> Models { get; private set; }
}
and this attached property:
public static class AttachedProperties
{
public static int GetSortOrder(DependencyObject obj)
{
return (int)obj.GetValue(SortOrderProperty);
}
public static void SetSortOrder(DependencyObject obj, int value)
{
obj.SetValue(SortOrderProperty, value);
}
public static readonly DependencyProperty SortOrderProperty =
DependencyProperty.RegisterAttached("SortOrder", typeof(int), typeof(AttachedProperties), new PropertyMetadata(0));
}
I have the following DataTemplate definitions (over-simplified):
<DataTemplate DataType="{x:Type local:FirstModel}">
<StackPanel Background="Red" local:AttachedProperties.SortOrder="1">
<Label>First's Name:</Label>
<TextBlock Text="{Binding Name}" />
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type local:SecondModel}">
<StackPanel Background="Green" local:AttachedProperties.SortOrder="2">
<Label>Second's Name:</Label>
<TextBlock Text="{Binding Name}" />
</StackPanel>
</DataTemplate>
Somewhere the usage will be like:
<ItemsControl ItemsSource="{Binding Models}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" IsItemsHost="True" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
And here the order of the items should be based on the attached property I defined for the data templates. Don't see any option to use the CollectionViewSource directly here, may be I'm wrong...
Current options I see, none too appealing, are:
Attached behavior on the ItemsControl, traversing the visual tree of each new item and sorting the Items in accordance with the found SortOrder value
A custom ItemsControl with it's own sorting logic, panel, blackjack and... you know
Wrapping the model instances in some kind of proxy with SortOrder property on it. Which still requires some custom/user control code-behind or ViewModel class changes
Is there some better/easier way I miss?
I guess you cant
I think the only way is to implement your own ItemsControl
Or wrap the models with another class
Maybe this helps:
SortDescription with custom attached property