How to modify ControlTemplate to add items directly to my custom control - wpf

I have defined following control template for my custom control.
<ControlTemplate TargetType="{x:Type local:CustomControl}">
<Grid x:Name="MainGrid">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<local:CustomPanel x:Name="MyCustomPanel" Grid.Column="0" />
<ScrollBar Grid.Column="1" Width="20" />
</Grid>
</ControlTemplate>
Here the CustomPanel derives form Panel class. Now I cannot add the items to my CustomControl directly like this
<local:CustomControl x:Name="CControl" Grid.Row="1">
<Button/>
<Button/>
<Button/>
</local:CustomControl>
What can I do for adding the items to my custom control directly from XAML?

Use [ContentProperty(PropertyName)] on your CustomControl.
And: make sure that the content property initialized to an empty list (must not be null).
E.g.:
[ContentProperty("Items")]
public class CustomControl : UserControl
{
public static readonly DependencyProperty ItemsProperty =
DependencyProperty.Register("Items", typeof(UIElementCollection), typeof(CustomControl), new UIPropertyMetadata(null)));
public UIElementCollection Items
{
get { return (UIElementCollection) GetValue(ItemsProperty); }
set { SetValue(ItemsProperty, value); }
}
public CustomControl()
{
Items = new UIElementCollection();
}
}
IMPORTANT: Do not create the empty collection inside the dependency property registration, i.e. do not use this:
... new UIPropertyMetadata(new UIElementCollection())
This is considered bad practice, because you then would unintentionally create a singleton collection. Please see Collection-Type Dependency Properties for more details.

Here is a sample control that allows you to directly add content in the way that you're after.
The lines of interest here are the attribute on top of the MyCustomControl class, this tells the XAML editor which property any directly added content should be placed in.
In the XAML code the important line is the ItemsControl that's bound to the Items property, this actually displays each item.
C#
[ContentProperty("Items")]
public class MyCustomControl : Control
{
public ObservableCollection<Object> Items
{
get { return (ObservableCollection<Object>)GetValue(ItemsProperty); }
set { SetValue(ItemsProperty, value); }
}
public static readonly DependencyProperty ItemsProperty =
DependencyProperty.Register("Items", typeof(ObservableCollection<Object>), typeof(MyCustomControl), new UIPropertyMetadata(new ObservableCollection<object>()));
}
XAML
<Style TargetType="{x:Type local:MyCustomControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:MyCustomControl}">
<ItemsControl ItemsSource="{TemplateBinding Items}" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<local:MyCustomControl>
<Button />
<Button />
</local:MyCustomControl>

Related

Send Value from View Model to UserControl Dependency Property WPF

I have a dependency property in a UserControl with a property called SelectedColor. From my main app, the view of the window that uses this my code is:
<controls:ColorPicker SelectedColor="{Binding MyCanvas.CanvasBackgroundColor}" />
And the code from the view model is:
public MyCanvas { get; set; }
public MyWindowViewModel(MyCanvas myCanvas)
{
MyCanvas = myCanvas;
}
And then the XAML for my UserControl is:
<UserControl . . .>
<Button Click="Button_Click">
<Button.Style>
<Setter Property="Background" Value="Transparent" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border Background="{Binding SelectedColor}" BorderBrush="Black" BorderThickness="1" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Button.Style>
</Button>
</UserControl>
And the code-behind:
public ColorPicker()
{
InitializeComponent();
DataContext = this;
}
public SolidColorBrush SelectedColor
{
get { return (SolidColorBrush)GetValue(SelectedColorProperty); }
set { SetValue(SelectedColorProperty, value); }
}
public static readonly DependencyProperty SelectedColorProperty =
DependencyProperty.Register(nameof(SelectedColor), typeof(SolidColorBrush), new UIPropertyMetadata(null));
I think the problem might be with the line in the code-behind DataContext = this;. Is it correct that declaring this creates an entirely new context for the instance of this user control in the main app and therefore any values sent to it from the view model would be re-initialized? If so, how can I send the value over without it being re-declared? I also need the DataContext = this line because without it some functionality within my UserControl will no longer work.
Has anyone encountered this before?
Thanks in advance!
DataContext = this sets the DataContext of the UserControl to itself. You don't want to do this. Instead you could bind to a property of the UserControl using a {RelativeSource} without setting the DataContext property:
<Border Background="{Binding SelectedColor, RelativeSource={RelativeSource AncestorType=UserControl}}"
BorderBrush="Black" BorderThickness="1" />
Code-behind:
public ColorPicker()
{
InitializeComponent();
}
public SolidColorBrush SelectedColor
{
get { return (SolidColorBrush)GetValue(SelectedColorProperty); }
set { SetValue(SelectedColorProperty, value); }
}
public static readonly DependencyProperty SelectedColorProperty =
DependencyProperty.Register(nameof(SelectedColor), typeof(SolidColorBrush), new UIPropertyMetadata(null));

Binding to readonly property in generic.xaml for customControl (Button)

I'm doing a CustomControl (button) using generic.xaml and dependency properties.
Here is my generic.xaml code :
<Style TargetType="{x:Type local:FlatButton}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:FlatButton}">
<Grid MinHeight="50" MaxHeight="50" MinWidth="200" MaxWidth="200">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="3*" />
</Grid.ColumnDefinitions>
<Grid Grid.Column="0" Background="{TemplateBinding BackgroundDarker}">
</Grid>
<Grid Grid.Column="1" Background="{TemplateBinding Background}">
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" FontFamily="Segoe UI" Foreground="White" FontWeight="Bold" />
</Grid>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
My customControl class :
Public Shared Shadows BackgroundProperty As DependencyProperty = DependencyProperty.Register("Background", GetType(SolidColorBrush), GetType(FlatButton))
Public Overloads Property Background As SolidColorBrush
Get
Return CType(GetValue(BackgroundProperty), SolidColorBrush)
End Get
Set(value As SolidColorBrush)
SetValue(BackgroundProperty, value)
End Set
End Property
Public Shared BackgroundDarkerProperty As DependencyProperty = DependencyProperty.Register("BackgroundDarker", GetType(SolidColorBrush), GetType(FlatButton))
Public ReadOnly Property BackgroundDarker As SolidColorBrush
Get
Return Background.Darker
End Get
End Property
And finally how I use my control in a UserControl :
<Grid>
<local:FlatButton Background="Red" />
</Grid>
When I put "Red" in the xaml of my FlatButton, the right part is well colored in Red (in VS and in runtime), but what I want is that the left part colores itself automatically with Darker red (it's an extension which works). But it seems not to be colored. I've no binding error in output.
What am I doing wrong ?
Thanks all.
-----EDIT----- :
Ok, to do that I made a converter which convert the "Background" value to a darker color.
I templateBinded the background of the left grid to "Background" with an instance of my converter.
While a converter will work, this functionality belongs inside the FlatButton control code. Use the Background's PropertyChangedCallback to update BackgroundDarker.
public class FlatButton : Button
{
// Background
public static new DependencyProperty BackgroundProperty = DependencyProperty.Register("Background", typeof(SolidColorBrush), typeof(FlatButton), new PropertyMetadata(OnBackgroundChanged));
public new SolidColorBrush Background { get { return (SolidColorBrush)GetValue(BackgroundProperty); } set { SetValue(BackgroundProperty, value); } }
// BackgroundDarker
public static DependencyProperty BackgroundDarkerProperty = DependencyProperty.Register("BackgroundDarker", typeof(SolidColorBrush), typeof(FlatButton));
public SolidColorBrush BackgroundDarker { get { return (SolidColorBrush)GetValue(BackgroundDarkerProperty); } private set { SetValue(BackgroundDarkerProperty, value); } }
private static void OnBackgroundChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// update BackgroundDarker
var btn = (FlatButton)d;
btn.BackgroundDarker = btn.Background.Darker();
}
static FlatButton()
{
// lookless control, get default style from generic.xaml
DefaultStyleKeyProperty.OverrideMetadata(typeof(FlatButton), new FrameworkPropertyMetadata(typeof(FlatButton)));
}
}
public static class SolidColorBrushExtension
{
public static SolidColorBrush Darker(this SolidColorBrush brush)
{
const double perc = 0.6;
return new SolidColorBrush(Color.FromRgb((byte)(brush.Color.R * perc), (byte)(brush.Color.G * perc), (byte)(brush.Color.B * perc)));
}
}

WPF Textbox is collapsed when the content string is a single digit

I am working on an application that creates textboxes/combo boxes dynamically based on if there is a single replacement for a keyword, or multiple replacements, and adds them to a stackpanel. I have ran into an issue where if the string that is being populated into the textbox is a single digit, ie: "2" the textbox is collapsed. Here is the associated DependencyProperty and constructor for the view model:
#region Properties
public ObservableCollection<string> KeywordValueList
{
get { return (ObservableCollection<string>)GetValue(_KeywordValueListProperty); }
set { SetValue(_KeywordValueListProperty, value); }
}
private static readonly DependencyProperty _KeywordValueListProperty = DependencyProperty.Register("KeywordValueList",
typeof(ObservableCollection<string>),
typeof(KeywordControlViewModel),
new PropertyMetadata(null, null));
public string KeywordValue
{
get { return (string)GetValue(_KeywordValueProperty); }
set { SetValue(_KeywordValueProperty, value); }
}
private static readonly DependencyProperty _KeywordValueProperty = DependencyProperty.Register("KeywordValue",
typeof(string),
typeof(KeywordControlViewModel),
new PropertyMetadata(null, null));
#endregion
#region Constructors
public KeywordControlViewModel(string keyword, object keywordValue)
{
Keyword = keyword;
if (keywordValue is string)
{
KeywordValue = (string)keywordValue;
KeywordValueList = null;
}
else if (keywordValue is ICollection)
{
KeywordValue = null;
ObservableCollection<string> toSet = new ObservableCollection<string>(keywordValue as List<string>);
KeywordValueList = toSet;
}
else
{
KeywordValue = "-Not Set-";
KeywordValueList = null;
}
}
#endregion
This is the relevant portion of the xaml:
<Grid HorizontalAlignment="Stretch">
<Grid.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="../Styles/ControlsStyle.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Grid.Resources>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="180" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="30" />
<RowDefinition Height="25" />
</Grid.RowDefinitions>
<!-- Labels etc on Row 0, generating properly so omitting for space-->
<TextBox Name ="KeyWordTextBox"
Style="{StaticResource InputBoxStyle}"
Text="{Binding KeywordValue}"
Grid.Row="1"
Grid.Column="0"
Grid.ColumnSpan="3"
Visibility="{Binding KeywordValue, TargetNullValue=Hidden}">
<TextBox.ToolTip>
<Label Content="{Binding Keyword, StringFormat='Edit value for {0}'}" />
</TextBox.ToolTip>
</TextBox>
<ComboBox Name="KeyWordComboBox"
ItemsSource="{Binding KeywordValueList}"
Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="3" Visibility="{Binding KeywordValueList, TargetNullValue=Hidden}"/>
</Grid>
And the style:
<Style x:Key="InputBoxStyle" TargetType="TextBox">
<Setter Property="Margin" Value="0,0,5,0" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="Height" Value="20" />
</Style>
I have debugged quite a bit and found that if I change the single digit to either a single letter, string of more than one number, or string containing both it shows the textbox as expected. Also, the Visibility value for the textbox in the debugger shows Collapsed, not Hidden -- the TargetNullValue does not seem to be causing this. In fact the textbox does not show either if I change it to Visible. This only started happening when I added the option for a combobox, prior to that the textbox generated properly with a single digit.
Can anyone offer an idea why this may be happening?
I suppose that the code doesn't work because You are binding a string value (and ObservableCollection<string> value in case of combobox) to a Visibility property.
In order to hide controls on null value:
You can write a value converter similar to the one suggested here: https://stackoverflow.com/a/2123905/232530 (but just check if value parameter is null in the Convert method)
or
You can use triggers as suggested here: How to hide the empty TextBlock?.
Please let me know if You need help when using any of those solutions.

TemplateBinding to RowDefinition.Height ignored in ContentControl

Description:
I have a custom content control and I am trying to enable some external settings via dependency properties. Basically it's a decorator panel with two grid rows, upper one is the header, the lower one is the content (via ContentPresenter).
There are 3 items that are bound to the template (via TemplateBinding), HeaderHeight, TextSize and Header (each of them has its dependency property of an appropriate type).
Problem:
While two of the bindings work perfectly (even in design-time), the third one does not. The FontSize="{TemplateBinding TextSize}" and the Text="{TemplateBinding Header}" bindings work, but the <RowDefinition Height="{TemplateBinding HeaderHeight}" /> does not work.
The grid splits the rows' heights 50/50, no matter which value I set the HeaderHeight property to. It does not even take the default value from the DP metadata.
Question:
What is the problem with this scenario? Why do the other two bindings work with no problems and this one behaves as if there is no binding at all?
Note:
If I set DataContext = this in the constructor and replace {TemplateBinding HeaderHeight} with {Binding HeaderHeight}, the problem disappears and it works as intended. But I'd like to know why I don't need to do the same thing with other bindings to make them work.
XAML (Themes/Generic.xaml):
<Style TargetType="local:KaiPanel">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:KaiPanel">
<Grid x:Name="LayoutRoot">
<Grid.RowDefinitions>
<RowDefinition Height="{TemplateBinding HeaderHeight}" />
<RowDefinition />
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Border>
<TextBlock FontSize="{TemplateBinding TextSize}"
Text="{TemplateBinding Header}" />
</Border>
</Grid>
<Grid Grid.Row="1">
<Border>
<ContentPresenter />
</Border>
</Grid>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Content Control (C#):
public class KaiPanel : ContentControl
{
public KaiPanel()
{
this.DefaultStyleKey = typeof(KaiPanel);
}
public static readonly DependencyProperty TextSizeProperty =
DependencyProperty.Register("TextSize", typeof(double), typeof(KaiPanel), new PropertyMetadata(15.0));
public double TextSize
{
get { return (double)GetValue(TextSizeProperty); }
set { SetValue(TextSizeProperty, value); }
}
public static readonly DependencyProperty HeaderProperty =
DependencyProperty.Register("Header", typeof(String), typeof(KaiPanel), new PropertyMetadata(""));
public String Header
{
get { return (String)GetValue(HeaderProperty); }
set { SetValue(HeaderProperty, value); }
}
public static readonly DependencyProperty HeaderHeightProperty =
DependencyProperty.Register("HeaderHeight", typeof(GridLength), typeof(KaiPanel), new PropertyMetadata(new GridLength(40)));
public GridLength HeaderHeight
{
get { return (GridLength)GetValue(HeaderHeightProperty); }
set { SetValue(HeaderHeightProperty, value); }
}
}
Custom Control usage (XAML):
<UserControl ...>
<Grid x:Name="LayoutRoot">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<StackPanel x:Name="buttonsStackPanel" Grid.Column="0" VerticalAlignment="Center">
<!-- Some buttons here -->
</StackPanel>
<Grid Grid.Column="1">
<controls:KaiPanel x:Name="contentPanel">
<navigation:Frame x:Name="contentFrame" Source="KP">
<navigation:Frame.UriMapper>
<uriMapper:UriMapper>
<uriMapper:UriMapping Uri="KP" MappedUri="/Views/Kornelijepetak.xaml" />
<uriMapper:UriMapping Uri="KAI" MappedUri="/Views/KaiNetwork.xaml" />
</uriMapper:UriMapper>
</navigation:Frame.UriMapper>
</navigation:Frame>
</controls:KaiPanel>
</Grid>
</Grid>
</UserControl>
Sadly it seems what you're attempting to do requires more than just a single data binding. RowDefinition isn't a subclass of FrameworkElement, and it doesn't match any of the other criteria specified in the MSDN Silverlight data binding documentation, so it can't be used as the target of a binding.
What you want to do is possible, but unfortunately it involves a little more code.
Firstly, add a field for the main grid (I've called it mainGrid) to your KaiPanel class. Then, override the OnApplyTemplate method in this class to grab the main Grid from the template and keep a reference to it in your mainGrid field:
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
mainGrid = GetTemplateChild("LayoutRoot") as Grid;
SetHeaderRowHeight();
}
This calls a method that updates the height of the first row of the grid. That method is as follows:
private void SetHeaderRowHeight()
{
if (mainGrid != null)
{
mainGrid.RowDefinitions[0].Height = HeaderHeight;
}
}
I admit I'm not 100% sure that OnApplyTemplate is called after the DPs are set. It seems that this is the case (a quick test seemed to confirm this), but all I could find to back this up was this post on the Silverlight forums. If you find that this isn't the case, you'll need to register a PropertyChangedCallback on the HeaderHeight DP that will also call this SetHeaderRowHeight method.
See also http://forums.silverlight.net/forums/t/76992.aspx#183089.
Use RelativeSource and TemplatedParent instead:
<RowDefinition Height="{Binding RelativeSource={RelativeSource TemplatedParent},
Path=HeaderHeight}" />
Here the difference between TemplateBinding and RelativeSource TemplatedParent is explained:
WPF TemplateBinding vs RelativeSource TemplatedParent

Issue binding Image Source dependency property

I have created a custom control for ImageButton as
<Style TargetType="{x:Type Button}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Local:ImageButton}">
<StackPanel Height="Auto" Orientation="Horizontal">
<Image Margin="0,0,3,0" Source="{Binding ImageSource}" />
<TextBlock Text="{TemplateBinding Content}" />
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
ImageButton class looks like
public class ImageButton : Button
{
public ImageButton() : base() { }
public ImageSource ImageSource
{
get { return base.GetValue(ImageSourceProperty) as ImageSource; }
set { base.SetValue(ImageSourceProperty, value); }
}
public static readonly DependencyProperty ImageSourceProperty =
DependencyProperty.Register("Source", typeof(ImageSource), typeof(ImageButton));
}
However I'm not able to bind the ImageSource to the image as:
(This code is in UI Folder and image is in Resource folder)
<Local:ImageButton x:Name="buttonBrowse1" Width="100" Margin="10,0,10,0"
Content="Browse ..." ImageSource="../Resources/BrowseFolder.bmp"/>
But if i take a simple image it gets displayed if same source is specified.
Can anyone tell me what shall be done?
You need to replace the Binding in your ControlTemplate by a TemplateBinding, just as you did for the Content property:
<Image Margin="0,0,3,0" Source="{TemplateBinding ImageSource}" />
Furthermore, the definition of your DependencyProperty is not correct. The string should read ImageSource instead of just Source:
DependencyProperty.Register("ImageSource", typeof(ImageSource), ...
I do not know whether/where this name conflict causes any problems, but at least it is highly recommended to use the exact name of the actual CLR property.
EDIT: You will also have to change the TargetType of your Style to your ImageButton:
<Style TargetType="{x:Type Local:ImageButton}">

Resources