DependencyProperty for ItemsSource - c#

I have a XAML like:
<UserControl x:Class="Book.CustomControls.HeaderedComboBox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
Width="200" Height="50">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Margin="2,2" VerticalAlignment="Center" Text="{Binding Header}" FontWeight="{Binding HeaderFontWeight}"/>
<ComboBox Grid.Row="1" Margin="2,2" VerticalAlignment="Center" ItemsSource="{Binding ItemsSource, UpdateSourceTrigger=PropertyChanged}"
SelectedItem="{Binding SelectedItem, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
</UserControl>
The cs-file therefor is:
public partial class HeaderedComboBox : UserControl
{
public HeaderedComboBox()
{
this.InitializeComponent();
this.HeaderFontWeight = FontWeights.Normal;
}
public static readonly DependencyProperty HeaderProperty = DependencyProperty.Register("Header", typeof(string), typeof(HeaderedComboBox));
public static readonly DependencyProperty HeaderFontWeightProperty = DependencyProperty.Register("HeaderFontWeight", typeof(FontWeight), typeof(HeaderedComboBox));
public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register("ItemsSource", typeof(IEnumerable), typeof(HeaderedComboBox));
public static readonly DependencyProperty SelectedItemProperty = DependencyProperty.Register("SelectedItem", typeof(object), typeof(HeaderedComboBox));
public string Header
{
get { return (string)GetValue(HeaderProperty); }
set { SetValue(HeaderProperty, value); }
}
public FontWeight HeaderFontWeight
{
get { return (FontWeight)GetValue(HeaderFontWeightProperty); }
set { SetValue(HeaderFontWeightProperty, value); }
}
public IEnumerable ItemsSource
{
get { return (IEnumerable)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
public object SelectedItem
{
get { return GetValue(SelectedItemProperty); }
set { SetValue(SelectedItemProperty, value); }
}
}
I tried to use this UserControl in any Window with
<customControls:HeaderedComboBox Header="Category" ItemsSource="{Binding Categories}"/>
Categories is just an ObservableCollection<string>.
Unfortunately I don't see any items in the ComboBox. At the moment I have no idea, what I'm doing wrong. Any ideas?

System.Windows.Data Error: 40 : BindingExpression path error: 'Categories' property not found on 'object' ''HeaderedComboBox' (Name='')'. BindingExpression:Path=Categories; DataItem='HeaderedComboBox' (Name=''); target element is 'HeaderedComboBox' (Name=''); target property is 'ItemsSource' (type 'IEnumerable')
Your DataContext is set to HeaderedCombobox and there system is looking for a Categories. Property Header worked since you put a text into it without binding. Possible solution
<Window x:Class="WpfApplication5.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525"
xmlns:local="clr-namespace:WpfApplication5" Name="window">
<Grid>
<local:HeaderedComboBox Header="Category" ItemsSource="{Binding ElementName=window, Path=DataContext.Categories}"/>
</Grid>

Related

Expose a UserControls ContentTemplate

I'm trying to make my fist UserControl in C#. It is a TabControll with some quality of life improvements. The goal is to be able to use it in various projects, so it has to be as generic as possible.
So far I have exposed the ItemSource through a DependencyProperty. But I'm suck with how to do the same with the ContentTemplate Property.
Here's an example of my code so far:
XAML:
<UserControl ...>
<UserControl.Resources>
<!-- some styles and templates -->
</UserControl.Resources>
<TabControl ItemsSource="{Binding ItemsSource}" SelectedIndex="{Binding selectedIndex}"
Style="{StaticResource FixatedTabControl}" ItemTemplateSelector="{StaticResource myDataTemplateSelector}"/>
</UserControl>
The code behind:
public partial class DynamicTabControl : UserControl, INotifyPropertyChanged
{
public DynamicTabControl()
{
InitializeComponent();
this.DataContext = this;
}
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register("ItemsSource", typeof(IEnumerable<ITabItem>), typeof(DynamicTabControl));
public IEnumerable<ITabItem> ItemsSource
{
get { return (IEnumerable<ITabItem>)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
}
I can use the DynamicTabControl like so:
<Window x:Class="Demo.MainWindow"
...
xmlns:local="clr-namespace:Demo"
xmlns:foo="clr-namespace:DynamicTabUserControl"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<foo:DynamicTabControl x:Name="testTabContr" ItemsSource="{Binding data}">
</foo:DynamicTabControl>
</Grid>
</Window>
But how can I make it possible to alter/add the contenTemplate of the UserControl's TabControl?
I would like to get it to behave like such:
<Window x:Class="Demo.MainWindow"
...
xmlns:local="clr-namespace:Demo"
xmlns:foo="clr-namespace:DynamicTabUserControl"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<foo:DynamicTabControl x:Name="testTabContr" ItemsSource="{Binding data}">
<foo:DynamicTabControl.TabControl.ContentTemplate>
<DataTemplate>
<TextBox Text="{Binding someData}"/>
</DataTemplate>
</foo:DynamicTabControl.TabControl.ContentTemplate>
</foo:DynamicTabControl>
</Grid>
</Window>
I'm still learning, so please help me out.
Thank you in advance.
Add another dependency property to the UserControl:
public partial class DynamicTabControl : UserControl, INotifyPropertyChanged
{
public DynamicTabControl()
{
InitializeComponent();
}
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register("ItemsSource", typeof(IEnumerable<ITabItem>), typeof(DynamicTabControl));
public IEnumerable<ITabItem> ItemsSource
{
get { return (IEnumerable<ITabItem>)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
public static readonly DependencyProperty TabContentTemplateProperty =
DependencyProperty.Register("TabContentTemplate", typeof(DataTemplate), typeof(DynamicTabControl));
public DataTemplate TabContentTemplate
{
get { return (DataTemplate)GetValue(TabContentTemplateProperty); }
set { SetValue(TabContentTemplateProperty, value); }
}
}
...and bind to it:
<UserControl>
<UserControl.Resources>
<!-- some styles and templates -->
</UserControl.Resources>
<TabControl ItemsSource="{Binding ItemsSource, RelativeSource={RelativeSource AncestorType=UserControl}}"
Style="{StaticResource FixatedTabControl}"
ContentTemplate="{Binding TabContentTemplate, RelativeSource={RelativeSource AncestorType=UserControl}}"/>
</UserControl>
You can then set this property in the XAML markup of the window:
<foo:DynamicTabControl x:Name="testTabContr" ItemsSource="{Binding data}">
<foo:DynamicTabControl.TabContentTemplate>
<DataTemplate>
<TextBox Text="{Binding someData}"/>
</DataTemplate>
</foo:DynamicTabControl.TabContentTemplate>
</foo:DynamicTabControl>

UWP ICommand DependencyProperty cast error

I have a problem with binding an DependencyProperty of type ICommand.
This is my custom user control:
<UserControl x:Class="HexEditor.Controls.NavButton"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:HexEditor.Controls"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
d:DesignHeight="300"
d:DesignWidth="400"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
mc:Ignorable="d">
<ToggleButton x:Name="toggleWrap"
Width="140"
Height="48"
Background="Transparent"
BorderThickness="0"
Command="{Binding Command}"
CommandParameter="{Binding CommandParameter}"
IsChecked="{Binding IsChecked}">
<Grid Width="140">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<FontIcon Margin="6,0,0,0"
Glyph="{Binding Icon}" />
<TextBlock Grid.Column="1"
Margin="32,0,0,0"
Text="{Binding Title}" />
</Grid>
</ToggleButton>
</UserControl>
This is code behind of my control:
public sealed partial class NavButton : UserControl
{
public NavButton()
{
this.InitializeComponent();
}
public static readonly DependencyProperty IconProperty =
DependencyProperty.Register("Icon", typeof(string), typeof(FontIcon), new PropertyMetadata(""));
public static readonly DependencyProperty TitleProperty =
DependencyProperty.Register("Title", typeof(string), typeof(TextBlock), new PropertyMetadata(""));
public static readonly DependencyProperty IsCheckedProperty =
DependencyProperty.Register("IsChecked", typeof(bool), typeof(ToggleButton), new PropertyMetadata(""));
public static readonly DependencyProperty CommandProperty =
DependencyProperty.Register("Command", typeof(ICommand), typeof(ToggleButton), new PropertyMetadata(""));
public static readonly DependencyProperty CommandParameterProperty =
DependencyProperty.Register("CommandParameter", typeof(object), typeof(ToggleButton), new PropertyMetadata(""));
public string Icon
{
get { return (string)GetValue(IconProperty); }
set { SetValue(IconProperty, value); }
}
public string Title
{
get { return (string)GetValue(TitleProperty); }
set { SetValue(TitleProperty, value); }
}
public bool IsChecked
{
get { return Convert.ToBoolean(GetValue(IsCheckedProperty)); }
set { SetValue(IsCheckedProperty, value); }
}
public ICommand Command
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
public object CommandParameter
{
get { return GetValue(CommandParameterProperty); }
set { SetValue(CommandParameterProperty, value); }
}
}
And this is a page where i use my control:
<UserControl x:Class="HexEditor.Common.RootFrame"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Controls="using:HexEditor.Controls"
xmlns:Interactions="using:Microsoft.Xaml.Interactions.Core"
xmlns:Interactivity="using:Microsoft.Xaml.Interactivity"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:HexEditor.Common"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
d:DesignHeight="300"
d:DesignWidth="400"
DataContext="{Binding CommonViewModel,
Source={StaticResource Locator}}"
mc:Ignorable="d">
<Grid Background="White">
<SplitView x:Name="navSplitView"
Width="Auto"
DisplayMode="CompactOverlay"
OpenPaneLength="140">
<SplitView.Pane>
<StackPanel>
<ToggleButton x:Name="hamBtn"
Width="48"
Height="48"
BorderThickness="0">
<FontIcon Glyph="" />
</ToggleButton>
<Controls:NavButton Title="Home"
Command="{Binding NavigationCommand}"
CommandParameter="HomePage"
Icon=""
IsChecked="True" />
<Controls:NavButton Title="Settings"
Command="{Binding NavigationCommand}"
CommandParameter="SettingsPage"
Icon=""
IsChecked="False" />
</StackPanel>
</SplitView.Pane>
<SplitView.Content>
<Frame x:Name="NavFrame" />
</SplitView.Content>
<Interactivity:Interaction.Behaviors>
<Interactions:DataTriggerBehavior Binding="{Binding IsChecked,
ElementName=hamBtn}"
Value="True">
<Interactions:ChangePropertyAction PropertyName="IsPaneOpen"
TargetObject="{Binding ElementName=navSplitView}"
Value="True" />
</Interactions:DataTriggerBehavior>
<Interactions:DataTriggerBehavior Binding="{Binding IsChecked,
ElementName=hamBtn}"
Value="False">
<Interactions:ChangePropertyAction PropertyName="IsPaneOpen"
TargetObject="{Binding ElementName=navSplitView}"
Value="False" />
</Interactions:DataTriggerBehavior>
</Interactivity:Interaction.Behaviors>
</SplitView>
</Grid>
</UserControl>
Here's the ViewModel:
public class CommonViewModel : ViewModelBase
{
public CommonViewModel()
{
this.NavigationCommand = new RelayCommand<NavigationSource>(this.ExecuteNavigationCommand);
}
public ICommand NavigationCommand { get; private set; }
private void ExecuteNavigationCommand(NavigationSource state)
{
NavigationProvider.Instance.NavigateTo(state);
}
}
When compiling, exception appears:
Exception thrown: 'System.InvalidCastException' in HexEditor.exe
'HexEditor.exe' (CoreCLR: CoreCLR_UWP_Domain): Loaded 'C:\Users\Root\documents\visual studio 2015\Projects\HexEditor\HexEditor\bin\x86\Debug\AppX\System.Resources.ResourceManager.dll'. Module was built without symbols.
Exception thrown: 'Windows.UI.Xaml.Markup.XamlParseException' in HexEditor.exe
WinRT information: Failed to assign to property 'HexEditor.Controls.NavButton.Command'. [Line: 29 Position: 41]
An exception of type 'Windows.UI.Xaml.Markup.XamlParseException' occurred in HexEditor.exe but was not handled in user code
WinRT information: Failed to assign to property 'HexEditor.Controls.NavButton.Command'. [Line: 29 Position: 41]
Additional information: The text associated with this error code could not be found.
Failed to assign to property 'HexEditor.Controls.NavButton.Command'. [Line: 29 Position: 41]
What i'm doing wrong?
I think the problem is that you have default values of "" for dependency properties of type bool and ICommand (new PropertyMetadata("") in both cases). You can't cast a string to either one, and that will cause an exception to be thrown. IsChecked should default to false, and Command should default to null.
This may or may not be relevant to the exact issue you're asking about, but it matters too: The third parameter of DependencyProperty.Register() should be the type of the owning class. That's typeof(NavButton) in your case, for all of its dependency properties.

Universal App XAML binding not working on UserControl when inside another UserControl

I'm currently working on a Windows Universal App. I'm still quite new to XAML in general but have had some experience with it.
The issue I'm having is around binding inside a UserControl. I've looked around and can't find an answer to my specific issue.
I have a XAML page that is linked to a ViewModel, this all works fine. Then on that page I'm using a UserControl that is basically just a panel with a header that contains some content. In the content of that panel I have another UserControl that basically just consists of a Label and TextBox.
When I bind things from my ViewModel to the ContentPanel UserControl everything works fine, it picks up my ViewModel context and binds correctly.
However, When I try to bind to the LabelledTextbox UserControl that is contained withing the ContentPanel the binding fails because it is just looking for the property that is on the ViewModel on the ContentPanel instead.
See below for the code I have
Page.xaml
<!--Page.xaml-->
<cc:ContentPanel PanelHeading="LEFT FOOT: Measurements" PanelHeadingBackground="{StaticResource OPCare.PanelHeader}">
<StackPanel>
<cc:LabelledTextbox LabelText="Malleoli Width" Text="test" />
<cc:LabelledTextbox LabelText="Met Head Width" />
</StackPanel>
</cc:ContentPanel>
ContentPanel.xaml
<!--ContentPanel UserControl-->
<UserControl
x:Class="OrthoticTabletApp.Controls.ContentPanel"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:OrthoticTabletApp.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="400"
x:Name="Parent">
<Grid DataContext="{Binding ElementName=Parent}">
<Grid.RowDefinitions>
<RowDefinition Height="50" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid Padding="10" Grid.Column="0" Grid.Row="0" Height="50" Background="{Binding Path=PanelHeadingBackground}">
<TextBlock Height="30" LineHeight="30" Text="{Binding Path=PanelHeading}" />
</Grid>
<Grid Padding="10" Grid.Column="0" Grid.Row="1" Background="White">
<ContentPresenter Content="{Binding Path=PanelBody}" />
</Grid>
</Grid>
</UserControl>
ContentPanel.xaml.cs
[ContentProperty(Name = "PanelBody")]
public sealed partial class ContentPanel : UserControl
{
public static readonly DependencyProperty PanelHeadingProperty = DependencyProperty.Register("PanelHeading", typeof(string), typeof(ContentPanel), new PropertyMetadata(""));
public string PanelHeading
{
get
{
return (string)GetValue(PanelHeadingProperty);
}
set
{
SetValue(PanelHeadingProperty, value);
}
}
public static readonly DependencyProperty PanelBodyProperty = DependencyProperty.Register("PanelBody", typeof(object), typeof(ContentPanel), new PropertyMetadata(null));
public object PanelBody
{
get
{
return (object)GetValue(PanelBodyProperty);
}
set
{
SetValue(PanelBodyProperty, value);
}
}
public Brush PanelHeadingBackground
{
get { return (Brush)GetValue(PanelHeadingBackgroundProperty); }
set { SetValue(PanelHeadingBackgroundProperty, value); }
}
public static readonly DependencyProperty PanelHeadingBackgroundProperty =
DependencyProperty.Register("PanelHeadingBackground", typeof(Brush), typeof(ContentPanel), new PropertyMetadata(null));
public ContentPanel()
{
this.InitializeComponent();
}
}
LabelledTextbox.xaml
<UserControl
x:Class="OrthoticTabletApp.Controls.LabelledTextbox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:OrthoticTabletApp.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="50"
d:DesignWidth="400"
x:Name="Parent">
<Grid DataContext="{Binding ElementName=Parent}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="300" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid Grid.Column="0" Padding="10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="15" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding Path=LabelText}" />
</Grid>
<Grid Grid.Column="1" Padding="10">
<TextBox Text="{Binding Path=Text}" />
</Grid>
</Grid>
</UserControl>
LabelledTextbox.xaml.cs
public sealed partial class LabelledTextbox : UserControl
{
public string LabelText
{
get { return (string)GetValue(LabelTextProperty); }
set { SetValue(LabelTextProperty, value); }
}
// Using a DependencyProperty as the backing store for LabelText. This enables animation, styling, binding, etc...
public static readonly DependencyProperty LabelTextProperty =
DependencyProperty.Register("LabelText", typeof(string), typeof(LabelledTextbox), new PropertyMetadata(null));
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
// Using a DependencyProperty as the backing store for Text. This enables animation, styling, binding, etc...
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(LabelledTextbox), new PropertyMetadata(null));
public LabelledTextbox()
{
this.InitializeComponent();
}
}
However, When I try to bind to the LabelledTextbox UserControl that is contained withing the ContentPanel the binding fails because it is just looking for the property that is on the ViewModel on the ContentPanel instead.
If I correctly understood what you've done, some properties of your ContentPanel are bound to the data model in the page's viewmodel, and properties of your LabelledTextbox are bound to the data model in other viewmodel?
If so, you can specify the DataContext for LabelledTextbox for StackPanel which is inside the ContentPanel, just for example like this:
<cc:ContentPanel PanelHeading="{x:Bind Heading}" PanelHeadingBackground="Azure">
<StackPanel>
<StackPanel.DataContext>
<local:LabelledTextboxViewModel x:Name="VM" />
</StackPanel.DataContext>
<cc:LabelledTextbox LabelText="{x:Bind VM.Lable1}" Text="test" />
<cc:LabelledTextbox LabelText="{x:Bind VM.Lable2}" />
</StackPanel>
</local:ContentPanel>
In your page's viewmodel, you can make the data model for ContentPanel and initialize the data for example like this:
public BlankPage3ViewModel()
{
Heading = "LEFT FOOT: Measurements";
}
public string Heading { get; set; }
In the LabelledTextboxViewModel you can code for example like this:
public class LabelledTextboxViewModel
{
public LabelledTextboxViewModel()
{
Lable1 = "Malleoli Width";
Lable2 = "Met Head Width";
}
public string Lable1 { get; set; }
public string Lable2 { get; set; }
}
Usually when we follow the MVVM pattern to develop a project, the data model should not be included inside of the viewmodel, I'm here just for clear and easy delivery, the key point is that you can specify different DataContext for different controls in the same page.

Inherit DependencyProperties

I have created several UserControls like a HeaderedDatePicker.
The XAML of this UserControl looks like:
<UserControl x:Class="Book.CustomControls.HeaderedDatePicker"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
Width="200" Height="50">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Margin="2,2" VerticalAlignment="Center" Text="{Binding Header}" FontWeight="{Binding HeaderFontWeight}"/>
<DatePicker Grid.Row="1" Margin="2,2" VerticalAlignment="Center" SelectedDate="{Binding Date, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
</UserControl>
The Code-Behind with the registered DependencyPropertys is:
public partial class HeaderedDatePicker : UserControl
{
public HeaderedDatePicker()
{
this.InitializeComponent();
this.HeaderFontWeight = FontWeights.Normal;
this.Date = DateTime.Now;
}
public static readonly DependencyProperty HeaderProperty = DependencyProperty.Register("Header", typeof(string), typeof(HeaderedDatePicker));
public static readonly DependencyProperty HeaderFontWeightProperty = DependencyProperty.Register("HeaderFontWeight", typeof(FontWeight), typeof(HeaderedDatePicker));
public static readonly DependencyProperty DateProperty = DependencyProperty.Register("Date", typeof(DateTime), typeof(HeaderedDatePicker));
public string Header
{
get { return (string)GetValue(HeaderProperty); }
set { SetValue(HeaderProperty, value); }
}
public FontWeight HeaderFontWeight
{
get { return (FontWeight)GetValue(HeaderFontWeightProperty); }
set { SetValue(HeaderFontWeightProperty, value); }
}
public DateTime Date
{
get { return (DateTime)GetValue(DateProperty); }
set { SetValue(DateProperty, value); }
}
}
Everything is working fine. Now I want to create another HeaderedControl like a HeaderedComboBox or so. My question now is:
Do I have to write the HeaderProperty and HeaderFontWeightProperty in each Code-Behind-File or is there a way to do this in a base-class?
I tried to create a base-class where the properties are registered, but in the code-behind of the HeaderedDatePicker I couldn't inherit from my class
public class main : UserControl
{
public static readonly DependencyProperty HeaderProperty = DependencyProperty.Register("Header", typeof(string), typeof(main));
public static readonly DependencyProperty HeaderFontWeightProperty = DependencyProperty.Register("HeaderFontWeight", typeof(FontWeight), typeof(main));
public static readonly DependencyProperty DateProperty = DependencyProperty.Register("Date", typeof(DateTime), typeof(main));
public string Header
{
get { return (string)GetValue(HeaderProperty); }
set { SetValue(HeaderProperty, value); }
}
public FontWeight HeaderFontWeight
{
get { return (FontWeight)GetValue(HeaderFontWeightProperty); }
set { SetValue(HeaderFontWeightProperty, value); }
}
public DateTime Date
{
get { return (DateTime)GetValue(DateProperty); }
set { SetValue(DateProperty, value); }
}
}
Then in every UserControl you are supposed to derive from previously declared main.
public partial class HeaderedComboBox : main
{
public HeaderedComboBox()
{
this.InitializeComponent();
this.DataContext = this;
}
}
Finally
<local:main x:Class="WpfApplication5.HeaderedComboBox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local ="clr-namespace:WpfApplication5"
mc:Ignorable="d"
Width="200" Height="50">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Margin="2,2" VerticalAlignment="Center" Text="{Binding Header}" FontWeight="{Binding HeaderFontWeight}"/>
<ComboBox Grid.Row="1" Margin="2,2" VerticalAlignment="Center" ItemsSource="{Binding ItemsSource, UpdateSourceTrigger=PropertyChanged}"
SelectedItem="{Binding SelectedItem, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
Let me know whether it works.
You can register attached property. See this link. Attached property can be used for any dependency object.

CustomControl with a ComboBox

I'm having troubles creating a custom control with a ComboBox.
This is my simple code:
public class MyComboBox : Control
{
public IEnumerable ItemsSource
{
get { return (IEnumerable)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
// Using a DependencyProperty as the backing store for ItemsSource. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register("ItemsSource", typeof(IEnumerable), typeof(MyComboBox), new UIPropertyMetadata(null));
public string DisplayMemberPath
{
get { return (string)GetValue(DisplayMemberPathProperty); }
set { SetValue(DisplayMemberPathProperty, value); }
}
// Using a DependencyProperty as the backing store for DisplayMemberPath. This enables animation, styling, binding, etc...
public static readonly DependencyProperty DisplayMemberPathProperty =
DependencyProperty.Register("DisplayMemberPath", typeof(string), typeof(MyComboBox), new UIPropertyMetadata(""));
public string SelectedValuePath
{
get { return (string)GetValue(SelectedValuePathProperty); }
set { SetValue(SelectedValuePathProperty, value); }
}
// Using a DependencyProperty as the backing store for SelectedValuePath. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SelectedValuePathProperty =
DependencyProperty.Register("SelectedValuePath", typeof(string), typeof(MyComboBox), new UIPropertyMetadata(""));
public object SelectedValue
{
get { return (object)GetValue(SelectedValueProperty); }
set { SetValue(SelectedValueProperty, value); }
}
// Using a DependencyProperty as the backing store for SelectedValue. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SelectedValueProperty =
DependencyProperty.Register("SelectedValue", typeof(object), typeof(MyComboBox), new UIPropertyMetadata(null));
public int SelectedIndex
{
get { return (int)GetValue(SelectedIndexProperty); }
set { SetValue(SelectedIndexProperty, value); }
}
// Using a DependencyProperty as the backing store for SelectedIndex. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SelectedIndexProperty =
DependencyProperty.Register("SelectedIndex", typeof(int), typeof(MyComboBox), new UIPropertyMetadata(0));
static MyComboBox()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MyComboBox), new FrameworkPropertyMetadata(typeof(MyComboBox)));
}
}
and this is its Generic.xaml:
<Style TargetType="{x:Type local:MyComboBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:MyComboBox}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="MyComboBox" />
<ComboBox Grid.Column="1"
ItemsSource="{Binding Path=ItemsSource, RelativeSource={RelativeSource Mode=TemplatedParent}}"
SelectedIndex="{Binding Path=SelectedIndex, RelativeSource={RelativeSource Mode=TemplatedParent}}"
DisplayMemberPath="{Binding Path=DisplayMemberPath, RelativeSource={RelativeSource Mode=TemplatedParent}}"
SelectedValuePath="{Binding Path=SelectedValuePath, RelativeSource={RelativeSource Mode=TemplatedParent}}"
SelectedValue="{Binding Path=SelectedValue, RelativeSource={RelativeSource Mode=TemplatedParent}}">
</ComboBox>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
To test it, I made a simple WPF application with this MainWindow.xaml:
<Window x:Class="Example.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Example"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:ViewModel />
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<ComboBox Grid.Row="0" Grid.Column="0" Margin="4" VerticalAlignment="Center"
ItemsSource="{Binding Path=Numbers}"
DisplayMemberPath="Key"
SelectedValuePath="Value"
SelectedValue="{Binding Path=Number, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Grid.Row="0" Grid.Column="1" Margin="4" HorizontalAlignment="Center" VerticalAlignment="Center"
Text="{Binding Path=Number, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<local:MyComboBox Grid.Row="1" Grid.Column="0" Margin="4" VerticalAlignment="Center"
ItemsSource="{Binding Path=MyNumbers}"
DisplayMemberPath="Key"
SelectedValuePath="Value"
SelectedValue="{Binding Path=MyNumber, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Grid.Row="1" Grid.Column="1" Margin="4" HorizontalAlignment="Center" VerticalAlignment="Center"
Text="{Binding Path=MyNumber, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</Grid>
</Window>
and this ViewModel:
public class ViewModel : INotifyPropertyChanged
{
private int _number;
public int Number
{
get { return _number; }
set
{
_number = value;
OnPropertyChanged("Number");
}
}
public Dictionary<string, int> Numbers { get; set; }
private int _myNumber;
public int MyNumber
{
get { return _myNumber; }
set
{
_myNumber = value;
OnPropertyChanged("MyNumber");
}
}
public Dictionary<string, int> MyNumbers { get; set; }
public ViewModel()
{
Numbers = new Dictionary<string, int>()
{
{ "One", 1 },
{ "Two", 2 },
{ "Three", 3 }
};
Number = 1;
MyNumbers = new Dictionary<string, int>()
{
{ "Four", 4 },
{ "Five", 5 },
{ "Six", 6 }
};
MyNumber = 4;
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string name)
{
PropertyChangedEventHandler e = PropertyChanged;
if (e != null)
{
e(this, new PropertyChangedEventArgs(name));
}
}
}
When I start it, my custom control has a red border and the output Window of Visual Studio signal this error:
System.Windows.Data Error: 23 : Cannot convert '[Four, 4]' from type 'KeyValuePair`2' to type 'System.Int32' for 'en-US' culture with default conversions; consider using Converter property of Binding. NotSupportedException:'System.NotSupportedException: Int32Converter cannot convert from System.Collections.Generic.KeyValuePair`2[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]].
at System.ComponentModel.TypeConverter.GetConvertFromException(Object value)
at System.ComponentModel.TypeConverter.ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, Object value)
at System.ComponentModel.BaseNumberConverter.ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, Object value)
at MS.Internal.Data.DefaultValueConverter.ConvertHelper(Object o, Type destinationType, DependencyObject targetElement, CultureInfo culture, Boolean isForward)'
System.Windows.Data Error: 7 : ConvertBack cannot convert value '[Four, 4]' (type 'KeyValuePair`2'). BindingExpression:Path=MyNumber; DataItem='ViewModel' (HashCode=55591935); target element is 'MyComboBox' (Name=''); target property is 'SelectedValue' (type 'Object') NotSupportedException:'System.NotSupportedException: Int32Converter cannot convert from System.Collections.Generic.KeyValuePair`2[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]].
at MS.Internal.Data.DefaultValueConverter.ConvertHelper(Object o, Type destinationType, DependencyObject targetElement, CultureInfo culture, Boolean isForward)
at MS.Internal.Data.ObjectTargetConverter.ConvertBack(Object o, Type type, Object parameter, CultureInfo culture)
at System.Windows.Data.BindingExpression.ConvertBackHelper(IValueConverter converter, Object value, Type sourceType, Object parameter, CultureInfo culture)'
The problem disappear when I remove the property SelectedIndex from the Generic.xaml, but I need it because I want that who will use my control, can have the same basic functionality of a ComboBox.
Anyone knows how to solve it?
I find the solution by myself.
The problem is on the default value I inserted for SelectedIndex: it must be -1 and not 0.
At first it seemed to me your problem is in SelectedValue. In you VM it has type of int, but WPF expects it to be KeyValuePair. The type of the collection's item.
ItemsSource="{Binding Path=MyNumbers}" // KeyValuePair
DisplayMemberPath="Key"
SelectedValuePath="Value"
SelectedValue="{Binding Path=MyNumber, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" // int
Then I realized my mistake that SelectedItem has to be of KeyValuePair type.
Error message looks as WPF does not look through the items' property given by the SelectedValuePath but tries to conver it explicitly to KeyValue pair. That is not documented behavior.

Categories

Resources