The UserControl below works well but I would like to make it easier to change the Style.
One thing I have tried is to convert this to a Custom Control, but I am stuck on basics like how to set the ToolTip inside the static method that deals with a change in a property (see below)
The other thing I tried to move the Style into a generic button style in the ResourceDictionary, but that is the subject of this question
How can I set the ToolTip in my subclassed Button?
Cheers
UserControl XAML:
<UserControl.Resources>
<ResourceDictionary Source="pack://application:,,,/Smack.Core.Presentation.Wpf;component/Themes/generic.xaml" />
</UserControl.Resources>
<Button x:Name="_button" Style="{StaticResource blueButtonStyle}" Command="{Binding AddNewItemCommand}" >
<StackPanel Orientation="Horizontal" >
<Image Source="{resx:Resx ResxName=Smack.Core.Presentation.Resources.MasterDetail, Key=bullet_add}" Stretch="Uniform" VerticalAlignment="Center" />
<AccessText x:Name="_accesText" VerticalAlignment="Center">_Add New Subject</AccessText>
<ContentPresenter/>
</StackPanel>
</Button>
UserControl Code Behind:
public partial class AddNewItemButton : UserControl
{
public AddNewItemButton() { InitializeComponent(); }
public static readonly DependencyProperty SubjectProperty = DependencyProperty.Register(
"Subject", typeof (string), typeof (AddNewItemButton),
new FrameworkPropertyMetadata(OnSubjectChanged));
public string Subject { get { return (string) GetValue(SubjectProperty); } set { SetValue(SubjectProperty, value); } }
private static void OnSubjectChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args) {
var control = obj as AddNewItemButton;
if (control == null) return;
control._accesText.Text = "_" + string.Format(MasterDetail.Subject_AddNew_Label, control.Subject.Capitalize());
control._button.ToolTip = string.Format(MasterDetail.Subject_AddNew_ToolTip, control.Subject.ToLower());
}
}
Failed attempt to create a Custom Control:
public class MyButton : Button
{
public static readonly DependencyProperty SubjectProperty = DependencyProperty.Register(
"ItemName", typeof(string), typeof(MyButton),
new FrameworkPropertyMetadata(OnSubjectChanged));
public string Subject
{
get { return (string)GetValue(SubjectProperty); }
set { SetValue(SubjectProperty, value); }
}
private static void OnSubjectChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
var control = obj as MyButton;
if (control == null) return;
ToolTip = ??;
}
}
UPDATE
Based on Phil's answer, the control (the bottom one) is more 'lookless' than I'd like :--)
Result
Code
public class AddNewItemButton : Button
{
static AddNewItemButton() {
var type = typeof (AddNewItemButton);
DefaultStyleKeyProperty.OverrideMetadata(type, new FrameworkPropertyMetadata(type));
}
#region Subject
public static readonly DependencyProperty SubjectProperty = DependencyProperty.Register(
"Subject", typeof(string), typeof(AddNewItemButton),
new PropertyMetadata(default(string)));
public string Subject
{
get { return (string)GetValue(SubjectProperty); }
set { SetValue(SubjectProperty, value); }
}
#endregion
}
Generic.xaml
<Style TargetType="{x:Type local:AddNewItemButton}">
<Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self}, Path=Subject, Converter={StaticResource AddNewItemForToolTip}}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:AddNewItemButton}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Image Stretch="Uniform" VerticalAlignment="Center"
Source="{resx:Resx ResxName=Smack.Core.Presentation.Resources.MasterDetail, Key=bullet_add}" />
<AccessText
Text="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Path=Subject, Converter={StaticResource AddNewItemForLabel}}" />
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Here's an example of a custom button with a tooltip (based on the questions you've been asking recently):
This is the code
public class CustomButton : Button
{
static CustomButton()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomButton),
new FrameworkPropertyMetadata(typeof(CustomButton)));
}
public static readonly DependencyProperty SubjectProperty =
DependencyProperty.Register("Subject", typeof (string),
typeof (CustomButton), new PropertyMetadata(default(string)));
public string Subject
{
get { return (string) GetValue(SubjectProperty); }
set { SetValue(SubjectProperty, value); }
}
}
This goes in Themes/generic.xaml
<System:String x:Key="Test">Add new: </System:String>
<Style TargetType="{x:Type local:CustomButton}">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={RelativeSource Self}, Path=Subject}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:CustomButton}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Label Grid.Column="0" Content="Image here"
VerticalAlignment="Center" Padding="0,0,5,0"/>
<AccessText Grid.Column="1" VerticalAlignment="Center">
<AccessText.Text>
<MultiBinding StringFormat="{}_{0} {1}">
<Binding Source="{StaticResource Test}"/>
<Binding RelativeSource=
"{RelativeSource TemplatedParent}"
Path="Subject"/>
</MultiBinding>
</AccessText.Text>
</AccessText>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Related
I have a custom ContentControl as follows.
Modal.xaml:
<Style TargetType="local:Modal">
<Setter Property="Background" Value="#65000000" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:Modal">
<Grid Background="{TemplateBinding Background}"
Padding="{TemplateBinding Padding}"
Visibility="{Binding IsOpen, RelativeSource={RelativeSource Mode=TemplatedParent}, Converter={StaticResource BoolToVisibilityConverter}}">
<Border x:Name="ContentBorder"
Padding="80">
<ContentControl HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch">
<Grid x:Name="ContentGrid"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
CornerRadius="10">
<ContentPresenter Content="{TemplateBinding Content}"/>
</Grid>
</ContentControl>
</Border>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
How can I access the ContentBorder or ContentGrid controls inside of the code behind. I am saying if (control.GetTemplateChild("ContentBorder") is Border border) and it can't find it. Please help.
Code Behind is as follows:
Modal.cs:
public class Modal : ContentControl
{
public Modal()
{
DefaultStyleKey = typeof(Modal);
}
public static readonly DependencyProperty IsOpenProperty =
DependencyProperty.Register(nameof(IsOpen), typeof(bool), typeof(Modal), new PropertyMetadata(false));
public bool IsOpen
{
get => (bool)GetValue(IsOpenProperty);
set => SetValue(IsOpenProperty, value);
}
public static readonly DependencyProperty DialogMaxWidthProperty =
DependencyProperty.Register(nameof(DialogMaxWidth), typeof(double), typeof(Modal), new PropertyMetadata(0, OnMaxWidthSet));
private static void OnMaxWidthSet(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var control = (Modal)d;
//GetTemplateChild is unable to find ContentBorder.
if (control.GetTemplateChild("ContentBorder") is Border border)
{
border.MaxWidth = (double)e.NewValue;
}
}
public double DialogMaxWidth
{
get => (double)GetValue(DialogMaxWidthProperty);
set => SetValue(DialogMaxWidthProperty, value);
}
}
I am using the custom control from somwhere outside as follows.
<controls:Modal x:Name="ContentModal"
Canvas.ZIndex="100"
Grid.Row="0"
Grid.RowSpan="2"
IsOpen="{x:Bind ViewModel.IsModalOpen, Mode=OneWay}"
DialogMaxWidth="450"/>
if DialogMaxWidth property change execute before OnApplyTemplate, GetTemplateChild will return null. so you must add a field and set it on OnApplyTemplate function. then check it on propertychaged
private Border brd;
public override void OnApplyTemplate()
{
brd = this.GetTemplateChild("ContentBorder") as Border;
if (brd!=null && DialogMaxWidth!=0)
brd.MaxWidth = DialogMaxWidth;
}
private static void OnMaxWidthSet(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var control = (Modal)d;
//GetTemplateChild is unable to find ContentBorder.
if (control.brd != null && control.brd is Border)
{
control.brd.MaxWidth = (double)e.NewValue;
}
}
Hi I'm building a control I need to access checkbox in the code behind But I get the null error
this is my control
<Style TargetType="TreeViewItem" x:Key="CheckTreeViewItem" >
<Setter Property="IsExpanded" Value="{Binding Path=IsExpanded,Mode=TwoWay}"/>
</Style>
<Style TargetType="local:CheckTreeView">
<Setter Property="ItemContainerStyle" Value="{StaticResource CheckTreeViewItem}"/>
<Setter Property="ItemTemplate">
<Setter.Value>
<HierarchicalDataTemplate DataType="{x:Type local:CheckTreeSource}" ItemsSource="{Binding Children}">
<CheckBox x:Name="PART_CheckBox" Margin="1" IsChecked="{Binding IsChecked}">
<TextBlock Text="{Binding Text}"/>
</CheckBox>
</HierarchicalDataTemplate>
</Setter.Value>
</Setter>
</Style>
And this is how I access control
[TemplatePart(Name = CheckBox_Key, Type = typeof(CheckBox))]
public partial class CheckTreeView : TreeView
{
private const string CheckBox_Key = "PART_CheckBox";
CheckBox checkBox;
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
checkBox = GetTemplateChild(CheckBox_Key) as CheckBox;
checkBox.Click += CheckBox_Click;
}
private void CheckBox_Click(object sender, RoutedEventArgs e)
{
}
}
When I use the following code I get no null error but no control in runtime
static CheckTreeView()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CheckTreeView),
new FrameworkPropertyMetadata(typeof(CheckTreeView)));
}
To subscribe to Click event of each CheckBox:
public partial class CheckTreeView : TreeView
{
public CheckTreeView()
{
InitializeComponent();
processNode(this);
}
void processNode(ItemsControl node)
{
node.ItemContainerGenerator.StatusChanged += (sender, args) =>
{
ItemContainerGenerator itemContainerGenerator = ((ItemContainerGenerator)sender);
if (itemContainerGenerator.Status == GeneratorStatus.ContainersGenerated)
{
for (int i = 0; i < itemContainerGenerator.Items.Count; i++)
{
TreeViewItem treeViewItem =
(TreeViewItem) itemContainerGenerator.ContainerFromIndex(i);
treeViewItem.Loaded += (o, eventArgs) =>
{
CheckBox checkBox = FindVisualChild<CheckBox>(treeViewItem);
checkBox.Click += CheckBox_Click;
};
processNode(treeViewItem);
}
}
};
}
public static T FindVisualChild<T>(DependencyObject obj) where T : DependencyObject
{
if (obj != null)
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
{
var child = VisualTreeHelper.GetChild(obj, i);
if (child is T)
{
return (T)child;
}
T childItem = FindVisualChild<T>(child);
if (childItem != null) return childItem;
}
}
return null;
}
private void CheckBox_Click(object sender, RoutedEventArgs e)
{
}
}
Note that if you use ObservableCollection as items source, you should process changes in that collection: subscribe to events of added items, unsubscribe from events of removed items.
CheckBox_Key element is not a part of CheckTreeView.ControlTemplate. It will be a part in tree view nodes, multiple times. GetTemplateChild won't find it
I would say that intead of CheckBox in ItemTemplate for CheckTreeView, you need to change TreeViewItem.Template and add CheckBox there. It won't help with GetTemplateChild, but is is more logical approach to build reusable TreeView with chechboxes.
Here is an example. Check/Uncheck operations can be handled by specail Command in CheckTreeView class:
public class CheckTreeView: TreeView
{
public CheckTreeView()
{
CheckCommand = new RelayCommand<object>(o => MessageBox.Show(o?.ToString()));
}
public ICommand CheckCommand
{
get { return (ICommand)GetValue(CheckCommandProperty); }
set { SetValue(CheckCommandProperty, value); }
}
public static readonly DependencyProperty CheckCommandProperty =
DependencyProperty.Register("CheckCommand", typeof(ICommand), typeof(CheckTreeView), new PropertyMetadata(null));
}
relevant part of TreeViewItem template (use Edit template feature in WPF designer to get full template):
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TreeViewItem}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition MinWidth="19" Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<ToggleButton x:Name="Expander" ClickMode="Press" IsChecked="{Binding IsExpanded, RelativeSource={RelativeSource TemplatedParent}}" Style="{StaticResource ExpandCollapseToggleStyle}"/>
<Border x:Name="Bd" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Grid.Column="1" Padding="{TemplateBinding Padding}" SnapsToDevicePixels="true">
<StackPanel Orientation="Horizontal">
<CheckBox Command="{Binding CheckCommand, RelativeSource={RelativeSource AncestorType={x:Type local:CheckTreeView}}}"
CommandParameter="{Binding RelativeSource={RelativeSource Self}}"
Margin="0,0,4,0"/>
<ContentPresenter x:Name="PART_Header" ContentSource="Header" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
</StackPanel>
</Border>
<ItemsPresenter x:Name="ItemsHost" Grid.ColumnSpan="2" Grid.Column="1" Grid.Row="1"/>
</Grid>
</ControlTemplate/>
</Setter.Value>
</Setter>
</Style>
I have a c++ program that parse C# codes using Roslyn.
I need to convert my styles and custom controls to just "code-behind".
for example I have a simple custom control contains a button .
XAML Style :
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:CUSTOM_LIBRARY_PARSE">
<Style TargetType="{x:Type local:CustomControl1}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:CustomControl1}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Button Background="#FF487DF0" >
<Label VerticalContentAlignment="Center" HorizontalContentAlignment="Center" OpacityMask="#FFC3C3C3" Content="{Binding text_of_button_Value, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:CustomControl1}}}" />
</Button>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
Code-Behind of Control :
using System;
using System.Windows;
using System.Windows.Controls;
namespace CUSTOM_LIBRARY_PARSE
{
public class CustomControl1 : Control
{
public static readonly DependencyProperty text_of_button
= DependencyProperty.Register(
"text_of_button_Value",
typeof(string),
typeof(CustomControl1),
new PropertyMetadata(Environment.UserName)
);
public string text_of_button_Value
{
get { return (string)GetValue(text_of_button); }
set { SetValue(text_of_button, value); }
}
static CustomControl1()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomControl1), new FrameworkPropertyMetadata(typeof(CustomControl1)));
}
}
}
Now , I need to know how to embed xaml code in code behind as a string like :
string code_xaml = "<ResourceDictionary\n xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n xmlns:local=\"clr-namespace:CUSTOM_LIBRARY_PARSE\">\n <Style TargetType=\"{x:Type local:CustomControl1}\">\n <Setter Property=\"Template\">\n <Setter.Value>\n <ControlTemplate TargetType=\"{x:Type local:CustomControl1}\">\n <Border Background=\"{TemplateBinding Background}\"\n BorderBrush=\"{TemplateBinding BorderBrush}\"\n BorderThickness=\"{TemplateBinding BorderThickness}\">\n <Button Background=\"#FF487DF0\" >\n <Label VerticalContentAlignment=\"Center\" HorizontalContentAlignment=\"Center\" OpacityMask=\"#FFC3C3C3\" Content=\"{Binding text_of_button_Value, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:CustomControl1}}}\" />\n </Button>\n </Border>\n </ControlTemplate>\n </Setter.Value>\n </Setter>\n </Style>\n</ResourceDictionary>\n";
And next parse it with XamlParser and load it to customcontrol1
is that possible ?
thanks
The Answer is :
Create a public version of custom control :
public CustomControl1()
{
}
Create a public version of custom control :
public CustomControl1()
{
ResourceDictionary Parse_Resource = XamlReader.Parse(code_xaml) as ResourceDictionary;
this.Resources = Parse_Resource;
}
Solved :D
So I have a control like this simplified version:
<local:ImageMapField x:Class="ImageApp.WPF.Controls.ImageMapContentField"
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:ImageApp.WPF.Controls"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
x:Name="Me">
<Grid HorizontalAlignment="Left">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150" />
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Grid.Column="0" HorizontalAlignment="Stretch" Style="{DynamicResource BaseLabelStyle}">
<TextBlock Text="{Binding Header, RelativeSource={RelativeSource AncestorType=local:ImageMapContentField, Mode=FindAncestor}}" TextWrapping="WrapWithOverflow"></TextBlock>
</Label>
<StackPanel Grid.Column="1">
<Image />
<Border Margin="20,5,5,2">
<ContentPresenter Content="{Binding DataEntryContent, ElementName=Me}" />
</Border>
</StackPanel>
</Grid>
</local:ImageMapField>
And am using it like so:
<controls:ImageMapContentField Header="Foo Date"
FieldName="FooDate"
ImageSource="{Binding MyImage, Mode=TwoWay}"
ItemsSource="{Binding Map.Items}"
Zoom="{Binding MapFieldZoom}">
<controls:ImageMapContentField.DataEntryContent>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBox Grid.Column="0" Text="{Binding MyDate, StringFormat=MM/dd/yyyy, ValidatesOnDataErrors=True, NotifyOnValidationError=True}">
<controls:WatermarkService.Watermark>
<TextBlock>Date</TextBlock>
</controls:WatermarkService.Watermark>
</TextBox>
<TextBox Grid.Column="1" Text="{Binding MyTime, StringFormat=HH\:mm}">
<controls:WatermarkService.Watermark>
<TextBlock>Time</TextBlock>
</controls:WatermarkService.Watermark>
</TextBox>
</Grid>
</controls:ImageMapContentField.DataEntryContent>
</controls:ImageMapContentField>
The problem is that because I am not binding my model's property to something on the ImageMapContentField, Validation.HasError on the ImageMapContentField is always false and never triggers.
What I get instead is the default TextBox validation.
What I really want is the ImageMapContentField to have a pink background. This works for my other controls where I am binding directly to something, but I cannot get this to work for the controls that have a ContentPresenter.
I am hoping I am just missing something that would allow the parent to capture the validation.
As requested here is a minimal example of the issue:
MainWindow.xaml
<Window x:Class="WpfApp1.MainWindow"
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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<Style TargetType="local:CustomTextField">
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<Border BorderBrush="Red" BorderThickness="2" CornerRadius="2">
<AdornedElementPlaceholder/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="ToolTip" Value="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=(Validation.Errors).CurrentItem.ErrorContent}"/>
<Setter Property="Background" Value="LightPink"/>
</Trigger>
</Style.Triggers>
</Style>
<Style TargetType="local:CustomContentControl">
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<Border BorderBrush="Red" BorderThickness="2" CornerRadius="2">
<AdornedElementPlaceholder/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="ToolTip" Value="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=(Validation.Errors).CurrentItem.ErrorContent}"/>
<Setter Property="Background" Value="LightPink"/>
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Window.DataContext>
<local:MyModel />
</Window.DataContext>
<StackPanel>
<local:CustomTextField LabelText="Number 1" Value="{Binding Number1, Mode=TwoWay, NotifyOnValidationError=True, ValidatesOnDataErrors=True, ValidatesOnNotifyDataErrors=True}" />
<local:CustomContentControl LabelText="Number 2">
<local:CustomContentControl.DataEntryContent>
<TextBox Text="{Binding Number2, Mode=TwoWay, NotifyOnValidationError=True, ValidatesOnDataErrors=True, ValidatesOnNotifyDataErrors=True}" />
</local:CustomContentControl.DataEntryContent>
</local:CustomContentControl>
</StackPanel>
</Window>
CustomTextField.xaml
<UserControl x:Class="WpfApp1.CustomTextField"
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:WpfApp1"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
x:Name="Me">
<StackPanel>
<Label Content="{Binding ElementName=Me, Path=LabelText}" />
<TextBox Text="{Binding ElementName=Me, Path=Value}" />
</StackPanel>
</UserControl>
CustomTextField.cs
public partial class CustomTextField : UserControl
{
public static readonly DependencyProperty LabelTextProperty = DependencyProperty.Register(
"LabelText", typeof(string), typeof(CustomTextField), new PropertyMetadata(default(string)));
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register(
"Value", typeof(string), typeof(CustomTextField), new PropertyMetadata(default(string)));
public string Value
{
get { return (string) GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
public string LabelText
{
get { return (string) GetValue(LabelTextProperty); }
set { SetValue(LabelTextProperty, value); }
}
public CustomTextField()
{
InitializeComponent();
}
}
CustomContentControl.xaml
<UserControl x:Class="WpfApp1.CustomContentControl"
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:WpfApp1"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
x:Name="Me">
<Grid>
<StackPanel>
<Label Content="{Binding ElementName=Me, Path=LabelText}" />
<ContentPresenter Content="{Binding DataEntryContent, ElementName=Me}" />
</StackPanel>
</Grid>
</UserControl>
CustomContentControl.cs
public partial class CustomContentControl : UserControl
{
public static readonly DependencyProperty LabelTextProperty = DependencyProperty.Register(
"LabelText", typeof(string), typeof(CustomContentControl), new PropertyMetadata(default(string)));
public static readonly DependencyProperty DataEntryContentProperty = DependencyProperty.Register(
"DataEntryContent", typeof(object), typeof(CustomContentControl), new PropertyMetadata(default(object)));
public object DataEntryContent
{
get { return (object) GetValue(DataEntryContentProperty); }
set { SetValue(DataEntryContentProperty, value); }
}
public string LabelText
{
get { return (string) GetValue(LabelTextProperty); }
set { SetValue(LabelTextProperty, value); }
}
public CustomContentControl()
{
InitializeComponent();
}
}
MyModel.cs
public class MyModel : INotifyPropertyChanged
{
int _number1;
int _number2;
public int Number1
{
get { return _number1; }
set
{
_number1 = value;
OnPropertyChanged();
}
}
public int Number2
{
get { return _number2; }
set
{
_number2 = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
The WPF validation is already bubbling up to the parent control (even when the child control is inside a ContentPresenter) - Validation.ErrorEvent
The problem here is that even though the event bubbles up, the attached property Validation.HasError doesn't get updated - that's basically due to the fact that there is no error in the control's property bindings. And hence, you don't see the background change.
To rectify this - you can use this code:
Update style in MainWindow.xaml
<Style TargetType="local:CustomContentControl">
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<Border BorderBrush="Red" BorderThickness="2" CornerRadius="2">
<AdornedElementPlaceholder/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="HasErrors" Value="True">
<Setter Property="Background" Value="LightPink"/>
</Trigger>
</Style.Triggers>
</Style>
And, update CustomContentControl to add a HasErrors dependency property, and validation error event handler
public static readonly DependencyProperty HasErrorsProperty = DependencyProperty.Register("HasErrors", typeof(bool), typeof(CustomContentControl), new PropertyMetadata(false));
public bool HasErrors
{
get { return (bool)GetValue(HasErrorsProperty); }
set { SetValue(HasErrorsProperty, value); }
}
public CustomContentControl()
{
InitializeComponent();
Validation.AddErrorHandler(this, (s, args) => {
if (args.Action == ValidationErrorEventAction.Added)
{
this.ToolTip = args.Error.ErrorContent;
HasErrors = true;
}
else
{
this.ToolTip = null;
HasErrors = false;
}
});
}
And your background will be updated.
I'm learning WPF but I have a lot of Windows Forms background. I want to convert a WinForms custom control in which I did put a label and a textbox (making a TextField), with a property allowing to set percentage of width allocated to the label.
Now, in WPF, I'm a bit lost. Should I create a custom control that inherits from a grid and expose (how ?) the columns definition properties, or should I create a custom control that will "contain" a grid, and expose two properties "LabelWidth" and "ContentWidth", and bind the two column definitions to these properties ? (Thinking these properties would contain 1* and 3*).
Could someone show me an example of such construction to have a place to start?
You could create a UserControl with two dependency properties.
Please refer to the following sample code.
MyUserControl.xaml:
<UserControl x:Class="WpfApplication3.MyUserControl"
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:WpfApplication3"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{Binding LabelWidth, RelativeSource={RelativeSource AncestorType=UserControl}}" />
<ColumnDefinition Width="{Binding ContentWidth, RelativeSource={RelativeSource AncestorType=UserControl}}" />
</Grid.ColumnDefinitions>
<TextBlock Text="..." />
<TextBox Grid.Column="1" />
</Grid>
</UserControl>
MyUserControl.xaml.cs:
public partial class MyUserControl : UserControl
{
public MyUserControl()
{
InitializeComponent();
}
public static readonly DependencyProperty LabelWidthProperty =
DependencyProperty.Register("LabelWidth", typeof(System.Windows.GridLength),
typeof(MyUserControl));
public System.Windows.GridLength LabelWidth
{
get { return (System.Windows.GridLength)GetValue(LabelWidthProperty); }
set { SetValue(LabelWidthProperty, value); }
}
public static readonly DependencyProperty ContentWidthProperty =
DependencyProperty.Register("ContentWidth", typeof(System.Windows.GridLength),
typeof(MyUserControl));
public System.Windows.GridLength ContentWidth
{
get { return (System.Windows.GridLength)GetValue(ContentWidthProperty); }
set { SetValue(ContentWidthProperty, value); }
}
}
Sample usage:
<local:MyUserControl LabelWidth="1*" ContentWidth="5*" />
Dependency Properties Overview: https://msdn.microsoft.com/en-us/library/ms752914(v=vs.110).aspx
I think I managed to achieve what I wanted to do by understanding mm8's code, in particular RelativeSource={RelativeSource AncestorType=UserControl} :
Added a custom control.
FieldText.cs :
public class FieldText : Control
{
static FieldText()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(FieldText), new FrameworkPropertyMetadata(typeof(FieldText)));
}
public FieldText()
{
}
public static readonly DependencyProperty LabelLengthProperty =
DependencyProperty.Register("LabelLength", typeof(GridLength),
typeof(FieldText), new UIPropertyMetadata(new GridLength(25, GridUnitType.Star)));
public virtual GridLength LabelLength
{
get { return (GridLength)GetValue(LabelLengthProperty); }
set { SetValue(LabelLengthProperty, value); }
}
public static readonly DependencyProperty ContentLengthProperty =
DependencyProperty.Register("ContentLength", typeof(GridLength),
typeof(FieldText), new UIPropertyMetadata(new GridLength(75, GridUnitType.Star)));
public virtual GridLength ContentLength
{
get { return (GridLength)GetValue(ContentLengthProperty); }
set { SetValue(ContentLengthProperty, value); }
}
}
Generic.xaml :
<Style TargetType="{x:Type controls:FieldText}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type controls:FieldText}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Grid x:Name="grd" Margin="3px">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{Binding Path=LabelLength, RelativeSource={RelativeSource AncestorType=Control}}" />
<ColumnDefinition Width="{Binding Path=ContentLength, RelativeSource={RelativeSource AncestorType=Control}}" />
</Grid.ColumnDefinitions>
<Label x:Name="label" Grid.Column="0" Content="Field:" />
<TextBox x:Name="textbox" Grid.Column="1" MaxLines="1" TextWrapping="NoWrap" />
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Sample usage:
<controls:FieldText x:Name="fld1" LabelLength="25*" ContentLength="75*" />