I have a custom ContentControl
public class DataControl : ContentControl
{
public List<DataItem> Options
{
get { return (List<DataItem>)GetValue(OptionsProperty); }
set { SetValue(OptionsProperty, value); }
}
public static readonly DependencyProperty OptionsProperty =
DependencyProperty.Register("Options", typeof(List<DataItem>), typeof(DataControl));
public DataControl()
{
Options = new List<DataItem>();
}
public string Label
{
get { return (string)GetValue(LabelProperty); }
set { SetValue(LabelProperty, value); }
}
// Using a DependencyProperty as the backing store for Label. This enables animation, styling, binding, etc...
public static readonly DependencyProperty LabelProperty =
DependencyProperty.Register("Label", typeof(string), typeof(DataControl));
}
public class DataItem
{
public DataItem(string key, string value)
{
Key = key;
Value = value;
}
public string Key { get; set; }
public string Value { get; set; }
}
whose template is applied by the following Style:
<Style TargetType="{x:Type local:DataControl}" x:Key="DefaultStyle">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:DataControl}">
<StackPanel>
<ListBox ItemsSource="{TemplateBinding Options}" >
<ListBox.ItemTemplate>
<DataTemplate>
<Label Content="{Binding Key}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Label Content="{TemplateBinding Label}" />
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
If I use a XamlWriter to Save this style and then read it back again, the ItemsSource binding is lost, but the Content binding on the Label isn't.
Style style = Application.Current.TryFindResource("DefaultStyle") as Style;
string s = XamlWriter.Save(style);
Style secondStyle = XamlReader.Parse(s) as Style;
Is there a way to ensure the ItemsSource binding is serialized correctly or to add it back in easily?
This also occurs when trying to get the Style from a ResourceDictionary from another project, e.g.
ResourceDictionary styles = new ResourceDictionary();
styles.Source = new Uri(String.Format("pack://application:,,,/StyleCopyTest;component/Styles/{0}Styles.xaml", type));
return styles;
In the WPF source code the ItemsSource is defined as
[Bindable(true), CustomCategory("Content"), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public IEnumerable ItemsSource { get; set; }
So this cannot be serialized by XamlWriter.
So you will have to write your own serializer or use approach mentioned here
I found this class here in code project that helps you Serialize the ItemsControl Property binding:
using System;
using System.Linq;
using System.ComponentModel;
namespace GUIKonfigurator
{
using System.Windows.Controls;
public class ItemsControlTypeDescriptionProvider:TypeDescriptionProvider
{
private static readonly TypeDescriptionProvider defaultTypeProvider = TypeDescriptor.GetProvider(typeof(ItemsControl));
public ItemsControlTypeDescriptionProvider(): base(defaultTypeProvider)
{
}
public static void Register()
{
TypeDescriptor.AddProvider(new ItemsControlTypeDescriptionProvider(), typeof(ItemsControl));
}
public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType,object instance)
{
ICustomTypeDescriptor defaultDescriptor = base.GetTypeDescriptor(objectType, instance);
return instance == null ? defaultDescriptor: new ItemsControlCustomTypeDescriptor(defaultDescriptor);
}
}
internal class ItemsControlCustomTypeDescriptor: CustomTypeDescriptor
{
public ItemsControlCustomTypeDescriptor(ICustomTypeDescriptor parent): base(parent)
{
}
public override PropertyDescriptorCollection GetProperties()
{
PropertyDescriptorCollection pdc = new PropertyDescriptorCollection(base.GetProperties().Cast<PropertyDescriptor>().ToArray());
return ConvertPropertys(pdc);
}
public override PropertyDescriptorCollection GetProperties(Attribute[] attributes)
{
PropertyDescriptorCollection pdc = new PropertyDescriptorCollection(base.GetProperties(attributes).Cast<PropertyDescriptor>().ToArray());
return ConvertPropertys(pdc);
}
private PropertyDescriptorCollection ConvertPropertys(PropertyDescriptorCollection pdc)
{
PropertyDescriptor pd = pdc.Find("ItemsSource", false);
if (pd != null)
{
PropertyDescriptor pdNew = TypeDescriptor.CreateProperty(typeof(ItemsControl), pd, new Attribute[]
{
new DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Visible),
new DefaultValueAttribute("")
});
pdc.Add(pdNew);
pdc.Remove(pd);
}
return pdc;
}
}
}
You just need to register it like this after registering the BindingConvertor:
EditorHelper.Register<BindingExpression, BindingConvertor>();
ItemsControlTypeDescriptionProvider.Register();
Here a quick test I did, creating a ComboBox and Serializing it:
ComboBox cb = new ComboBox();
cb.Width = 100;
cb.Height = 20;
Binding b = new Binding("Model.Activity");
b.Source = this.DataContext;
cb.SetBinding(ComboBox.ItemsSourceProperty, b);
string xaml = _Serializer.SerializeControlToXaml(cb);
And here the resulting Xaml Including the ItemsSource binding:
<ComboBox Width="100" Height="20" ItemsSource="{Binding Path=Model.Activity}" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" />
Hope this helps, I still need to take some time to understand it but so far seems to be working...
Related
I am trying to hide an item in the Combobox when it has been selected and this is how my code looks like right now:
VeiwModel.cs
public class SortList
{
public string Key { get; set; }
public string Value { get; set; }
public bool IsSelectable { get; set; }
}
private void InitSortList()
{
ObservableCollection<SortList> sl = new ObservableCollection<SortList>();
foreach(var i in defaultSortList)
{
SortList s = new SortList();
s.Key = i.Key.ToString();
s.Value = i.Value.ToString();
s.IsSelectable = false;
sl.Add(s);
}
_items = sl;
}
private ObservableCollection<SortList> _items = new ObservableCollection<SortList>();
public ObservableCollection<SortList> Items
{
get {
return _items; }
}
private SortList _selectedSort;
public SortList SelectedItem
{
get { return _selectedSort; }
set
{
if(_selectedSort != value)
{
_selectedSort = value;
_selectedSort.IsSelectable = false;
PropertyChanged(this, new PropertyChangedEventArgs("SelectedItem"));
}
}
}
MainPage.xaml
<ComboBox Header="Sort 1" HorizontalAlignment="Stretch"
Name="Sort_1" SelectionChanged="comboSelectionChanged"
ItemsSource="{Binding Items, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
SelectedItem="{Binding SelectedItem, Mode=TwoWay}"
SelectedValuePath="Key"
DisplayMemberPath="Value"
>
<ComboBox.ItemContainerStyle>
<Style TargetType="ComboBoxItem" BasedOn="ComboBoxIem">
<Setter
Property="IsEnabled"
Value="{Binding Items.IsSelectable, Mode=TwoWay}" />
//Binding IsSelectable doesnt work either
</Style>
</ComboBox.ItemContainerStyle>
</ComboBox>
I am not sure how the Binding part works on the Setter property as I think it's not getting the IsSelectable property from the Items class....
Please refer to document here, UWP does not support bindings in Style Setters. It will not effect when you binding ItemContainerStyle style.
Windows Presentation Foundation (WPF) and Microsoft Silverlight supported the ability to use a Binding expression to supply the Value for a Setter in a Style. The Windows Runtime doesn't support a Binding usage for Setter.Value (the Binding won't evaluate and the Setter has no effect, you won't get errors, but you won't get the desired result either). When you convert XAML styles from Windows Presentation Foundation (WPF) or Microsoft Silverlight XAML, replace any Binding expression usages with strings or objects that set values, or refactor the values as shared {StaticResource} markup extension values rather than Binding -obtained values.
For this scenario, A workaround could be a helper class with attached properties for the source paths of the bindings. It will create the binding expression in code behind in a PropertyChangedCallback of the helper property.
I have edited your code and xaml, please refer to the following code the implement.
<Page.DataContext>
<local:ViewModel />
</Page.DataContext>
<Grid>
<ComboBox
Name="Sort_1"
HorizontalAlignment="Stretch"
DisplayMemberPath="Value"
Header="Sort 1"
ItemsSource="{Binding Items, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
SelectedItem="{Binding SelectedItem, Mode=TwoWay}"
SelectedValuePath="Key"
SelectionChanged="comboSelectionChanged">
<ComboBox.ItemContainerStyle>
<Style TargetType="ComboBoxItem">
<Setter Property="local:BindingHelper.IsEnable" Value="IsSelectable" />
</Style>
</ComboBox.ItemContainerStyle>
</ComboBox>
</Grid>
C# Code
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
}
private void comboSelectionChanged(object sender, SelectionChangedEventArgs e)
{
}
}
public class BindingHelper
{
public static string GetIsEnable(DependencyObject obj)
{
return (string)obj.GetValue(IsEnableProperty);
}
public static void SetIsEnable(DependencyObject obj, string value)
{
obj.SetValue(IsEnableProperty, value);
}
// Using a DependencyProperty as the backing store for IsEnable. This enables animation, styling, binding, etc...
public static readonly DependencyProperty IsEnableProperty =
DependencyProperty.RegisterAttached("IsEnable", typeof(string), typeof(BindingHelper), new PropertyMetadata(null, GridBindingPathPropertyChanged));
private static void GridBindingPathPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var propertyPath = e.NewValue as string;
if (propertyPath != null)
{
var bindingProperty =
e.Property == IsEnableProperty
? ComboBoxItem.IsEnabledProperty
: null;
BindingOperations.SetBinding(
obj,
bindingProperty,
new Binding { Path = new PropertyPath(propertyPath) });
}
}
}
public class ViewModel : INotifyPropertyChanged
{
public class SortList : INotifyPropertyChanged
{
public string Key { get; set; }
public string Value { get; set; }
private bool _isSelectable;
public bool IsSelectable
{
get { return _isSelectable; }
set
{
_isSelectable = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string PropertyName = null)
{
if (PropertyChanged != null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(PropertyName));
}
}
}
public ViewModel()
{
defaultSortList = new Dictionary<string, string>();
defaultSortList.Add("0", "item");
defaultSortList.Add("1", "item1");
defaultSortList.Add("2", "item2");
defaultSortList.Add("3", "item3");
InitSortList();
}
private Dictionary<string, string> defaultSortList;
private void InitSortList()
{
ObservableCollection<SortList> sl = new ObservableCollection<SortList>();
foreach (var i in defaultSortList)
{
SortList s = new SortList();
s.Key = i.Key.ToString();
s.Value = i.Value.ToString();
s.IsSelectable = true;
sl.Add(s);
}
_items = sl;
}
private ObservableCollection<SortList> _items = new ObservableCollection<SortList>();
public ObservableCollection<SortList> Items
{
get
{
return _items;
}
}
private SortList _selectedSort;
public event PropertyChangedEventHandler PropertyChanged;
public SortList SelectedItem
{
get { return _selectedSort; }
set
{
if (_selectedSort != value)
{
_selectedSort = value;
_selectedSort.IsSelectable = false;
PropertyChanged(this, new PropertyChangedEventArgs("SelectedItem"));
}
}
}
}
I have a sample program to recreate the issue I am having.
Control:
public class PropertyUpdateControl : Control
{
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register(
nameof(Value), typeof(double), typeof(PropertyUpdateControl),
new FrameworkPropertyMetadata(default(double), FrameworkPropertyMetadataOptions.None, ValueProperty_Changed));
public double Value
{
get { return (double)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
private static void ValueProperty_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var lPropertyUpdateControl = (PropertyUpdateControl) d;
lPropertyUpdateControl.Value_Changed((double)e.OldValue, (double)e.NewValue);
}
private void Value_Changed(double oldValue, double newValue)
{
this.Value = newValue;
this.Label = newValue.ToString();
}
public static readonly DependencyProperty LabelProperty = DependencyProperty.Register(
nameof(Label), typeof(string), typeof(PropertyUpdateControl), new PropertyMetadata("Label"));
public string Label
{
get { return (string)GetValue(LabelProperty); }
set { SetValue(LabelProperty, value); }
}
}
Main Window
public class MainWindowViewModel : ViewModelBase
{
private PropertyUpdateViewModel mPropertyUpdateViewModel;
public MainWindowViewModel()
{
this.PropertyUpdateViewModel = new PropertyUpdateViewModel();
this.ChangeCommand = new RelayCommand(this.ChangeImplementation);
}
public ICommand ChangeCommand { get; set; }
public void ChangeImplementation()
{
if (this.PropertyUpdateViewModel == null)
{
this.PropertyUpdateViewModel = new PropertyUpdateViewModel();
}
else
{
this.PropertyUpdateViewModel = null;
}
}
public PropertyUpdateViewModel PropertyUpdateViewModel
{
get { return this.mPropertyUpdateViewModel;}
set { this.Set(ref this.mPropertyUpdateViewModel, value); }
}
}
public class PropertyUpdateViewModel : ViewModelBase
{
private double mValue;
private static int sInstanceCounter = 0;
public PropertyUpdateViewModel()
{
Interlocked.Increment(ref sInstanceCounter);
this.Value = sInstanceCounter;
}
public double Value
{
get { return this.mValue; }
set { this.Set(ref this.mValue, value); }
}
}
XAML
<Window.Resources>
<local:MainWindowViewModel x:Key="mKeyMainWindowViewModel" />
<Style TargetType="{x:Type local:PropertyUpdateControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:PropertyUpdateControl}">
<TextBlock
Margin="25" Text="{TemplateBinding Label}"></TextBlock>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<DataTemplate x:Key="mDataTemplate" DataType="{x:Type local:PropertyUpdateViewModel}">
<local:PropertyUpdateControl
Value="{Binding Value}"></local:PropertyUpdateControl>
</DataTemplate>
</Window.Resources>
<Window.DataContext>
<StaticResource ResourceKey="mKeyMainWindowViewModel"/>
</Window.DataContext>
<Grid>
<StackPanel>
<ContentPresenter Content="{Binding PropertyUpdateViewModel}" ContentTemplate="{StaticResource mDataTemplate}"/>
<Button Content="Update Value" Command="{Binding ChangeCommand}"></Button>
</StackPanel>
</Grid>
So basically the control seems to only be updated once. The label will always read 1 even after setting the view model to null and then to a new instance of the PropertyUpdateViewModel which will have an incrementing value. Why is the PropertyUpdateControl Value_Changed only called once and never called again even after the datacontext changes?
I found the issue to be in
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register(
nameof(Value), typeof(double), typeof(PropertyUpdateControl),
new FrameworkPropertyMetadata(default(double), FrameworkPropertyMetadataOptions.None, ValueProperty_Changed));
It needs to be FrameworkPropertyMetadataOptions.BindsTwoWayByDefault.
This post has more info on binding for anyone interested https://www.tutorialspoint.com/wpf/wpf_data_binding.htm
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.
similar to my Labeled TextBox, which issues are resolved in:
Labeled TextBox in Windows Universal App
I got two issues in my Labeled Combobox, but first the Code:
Generic.xaml:
<Style TargetType="template:LabeledComboBox">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="template:LabeledComboBox">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBlock Text="{TemplateBinding Label}" FontWeight="Bold" VerticalAlignment="Center" Margin="10,0" />
<ComboBox x:Name="PART_ComboBox" ItemsSource="{TemplateBinding ItemsSource}" SelectedIndex="{TemplateBinding SelectedIndex}" SelectedValue="{TemplateBinding SelectedValue}" SelectedValuePath="{TemplateBinding SelectedValuePath}" DisplayMemberPath="{TemplateBinding DisplayMemberPath}" VerticalAlignment="Center" Margin="20,0,10,0" Grid.Row="1" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
LabeledComboBox.cs:
[TemplatePart(Name = "PART_ComboBox", Type = typeof(ComboBox))]
public sealed class LabeledComboBox : Control, IParameterReturnable
{
public static readonly DependencyProperty LabelProperty = DependencyProperty.Register("Label", typeof(string), typeof(LabeledComboBox), new PropertyMetadata(""));
public string Label
{
get { return GetValue(LabelProperty).ToString(); }
set { SetValue(LabelProperty, value); }
}
public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register("ItemsSource", typeof(object), typeof(LabeledComboBox), new PropertyMetadata(null));
public object ItemsSource
{
get { return GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
public static readonly DependencyProperty SelectedIndexProperty = DependencyProperty.Register("SelectedIndex", typeof(int), typeof(LabeledComboBox), new PropertyMetadata(default(int)));
public int SelectedIndex
{
get { return (int) GetValue(SelectedIndexProperty); }
set { SetValue(SelectedIndexProperty, value); }
}
public static readonly DependencyProperty SelectedValueProperty = DependencyProperty.Register("SelectedValue", typeof(object), typeof(LabeledComboBox), new PropertyMetadata(null));
public object SelectedValue
{
get { return GetValue(SelectedValueProperty); }
set { SetValue(SelectedValueProperty, value); }
}
public static readonly DependencyProperty SelectedValuePathProperty = DependencyProperty.Register("SelectedValuePath", typeof(string), typeof(LabeledComboBox), new PropertyMetadata(default(string)));
public string SelectedValuePath
{
get { return GetValue(SelectedValuePathProperty).ToString(); }
set { SetValue(SelectedValuePathProperty, value); }
}
public static readonly DependencyProperty DisplayMemberPathProperty = DependencyProperty.Register("DisplayMemberPath", typeof(string), typeof(LabeledComboBox), new PropertyMetadata(default(string)));
public string DisplayMemberPath
{
get { return GetValue(DisplayMemberPathProperty).ToString(); }
set { SetValue(DisplayMemberPathProperty, value); }
}
private ComboBox _comboBox;
public LabeledComboBox()
{
this.DefaultStyleKey = typeof(LabeledComboBox);
}
public LabeledComboBox(List<Parameter> parameterList)
{
this.Label = parameterList[0].DisplayName ?? "";
this.ItemsSource = parameterList;
this.SelectedValuePath = "DefaultValue";
this.DisplayMemberPath = "DefaultValue";
this.SelectedIndex = 0;
this.DefaultStyleKey = typeof(LabeledComboBox);
}
protected override void OnApplyTemplate()
{
base.OnApplyTemplate();
_comboBox = GetTemplateChild("PART_ComboBox") as ComboBox;
if (_comboBox != null)
{
_comboBox.SelectionChanged += OnComboBoxSelectionChanged;
if (_comboBox.Items != null)
{
this.SelectedIndex = 0;
_comboBox.SelectedValue = _comboBox.Items[this.SelectedIndex];
}
}
}
private void OnComboBoxSelectionChanged(object sender, SelectionChangedEventArgs e)
{
this.SelectedValue = _comboBox.SelectedValue;
}
public string GetKey()
{
return Label;
}
public string GetValue()
{
return SelectedValue.ToString();
}
}
It will be called in two different ways:
Dynamically in C#:
stackPanel.Add(new LabeledComboBox(parameterList));
Static in Xaml:
<templates:LabeledComboBox Label="Kategorien:" ItemsSource="{Binding ElementName=pageRoot, Path=FeedCategories}" DisplayMemberPath="Name" SelectedValuePath="Name" />
As I said before I got two issues with it:
How can I bind the SelectionChangedEvent to access it in Xaml || C#
As you can see, I try to preselect the first Item, which does not work and I don't know how to do it right
Thank you very much for all helpful and well meant answers in advance!
Instead of creating a custom control and recreating all needed dependency properties, I would suggest you use the Header and HeaderTemplate properties of the built in ComboBox, which will be displayed, just like in your LabeledComboBox, above the selection menu. Additionally the SelectionChanged event will be available.
So the usage in XAML would look like the following:
<ComboBox
DisplayMemberPath="Name"
Header="Kategorien:"
ItemsSource="{Binding ElementName=pageRoot, Path=FeedCategories}"
SelectedValuePath="Name"
SelectionChanged="OnSelectionChanged">
<ComboBox.HeaderTemplate>
<DataTemplate>
<TextBlock
Margin="10,0"
VerticalAlignment="Center"
FontWeight="Bold"
Text="{Binding}" />
</DataTemplate>
</ComboBox.HeaderTemplate>
</ComboBox>
But if you don't want to use the above method, to expose the selection changed event in your LabeledComboBox, add the following code:
private void OnComboBoxSelectionChanged(object sender, SelectionChangedEventArgs e)
{
this.SelectedValue = _comboBox.SelectedValue;
this.RaiseSelectionChanged(e);
}
public event EventHandler<SelectionChangedEventArgs> SelectionChanged;
private void RaiseSelectionChanged(SelectionChangedEventArgs args)
{
if (SelectionChanged != null)
{
SelectionChanged(this, args);
}
}
Then you can use the created SelectionChanged event from XAML.
I'm trying to develop user control with some nested properties that allows to use databinding to set it. For example, I have something like this:
// Top level control
public class MyControl : Control
{
public string TopLevelTestProperty
{
get { return (string)GetValue(TopLevelTestPropertyProperty); }
set { SetValue(TopLevelTestPropertyProperty, value); }
}
public static readonly DependencyProperty TopLevelTestPropertyProperty =
DependencyProperty.Register("TopLevelTestProperty", typeof(string), typeof
(MyControl), new UIPropertyMetadata(""));
// This property contains nested object
public MyNestedType NestedObject
{
get { return (MyNestedType)GetValue(NestedObjectProperty); }
set { SetValue(NestedObjectProperty, value); }
}
public static readonly DependencyProperty NestedObjectProperty =
DependencyProperty.Register("NestedObject", typeof(MyNestedType), typeof
(MyControl), new UIPropertyMetadata(null));
}
// Nested object's type
public class MyNestedType : DependencyObject
{
public string NestedTestProperty
{
get { return (string)GetValue(NestedTestPropertyProperty); }
set { SetValue(NestedTestPropertyProperty, value); }
}
public static readonly DependencyProperty NestedTestPropertyProperty =
DependencyProperty.Register("NestedTestProperty", typeof(string), typeof
(MyNestedType), new UIPropertyMetadata(""));
}
// Sample data context
public class TestDataContext
{
public string Value
{
get
{
return "TEST VALUE!!!";
}
}
}
...
this.DataContext = new TestDataContext();
...
XAML:
<local:mycontrol x:name="myControl" topleveltestproperty="{Binding Value}" >
<local:mycontrol.nestedobject>
<local:mynestedtype x:name="myNestedControl" nestedtestproperty="{Binding Value}" />
</local:mycontrol.nestedobject>
</local:mycontrol>
It works well for property TopLevelTestProperty, but it doesn't work for NestedTestProperty.
It seems that nested bindings do not work. Can anybody help me please? Is there any way to make such binding?
I think that it happens because of my nested object has no any reference to the top level object, so it cannot be resolved using MyControl's DataContext.
H.B. right, nested control does not inherit DataContext from mycontrol.
Tyr out setting it explicitly:
<local:mycontrol x:name="myControl"
topleveltestproperty="{Binding Value}" >
<local:mycontrol.nestedobject>
<local:mynestedtype x:name="myNestedControl"
DataContext="{Binding ElementName=myControl,
Path=DataContext}"
nestedtestproperty="{Binding Value}" />
</local:mycontrol.nestedobject>
</local:mycontrol>