I have a custom control in my WPF project, I've defined the style, the class, and I've called it in my xaml and made sure to include a reference. when I start the project 50% of the time the custom object renders perfectly, the other 50% it just doesn't show up, but is still interactable.
public class PermissionBox : Control
{
public PermissionBox()
{
DefaultStyleKey = typeof(PermissionBox);
}
public string PumpID
{
get
{
return (string)GetValue(PumpIDProperty);
}
set
{
SetValue(PumpIDProperty, value);
}
}
public static readonly DependencyProperty PumpIDProperty =
DependencyProperty.Register("PumpID", typeof(string), typeof(PermissionBox), new PropertyMetadata(string.Empty));
public string FuelType
{
get
{
return (string)GetValue(FuelTypeProperty);
}
set
{
SetValue(FuelTypeProperty, value);
}
}
public static readonly DependencyProperty FuelTypeProperty =
DependencyProperty.Register("FuelType", typeof(string), typeof(PermissionBox), new PropertyMetadata(string.Empty));
}
in Themes/Generic.xaml
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:POS">
<Style TargetType="{x:Type controls:PermissionBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type controls:PermissionBox}">
<Grid Background="Gray" Opacity="0.8">
<TextBlock Foreground="Black" FontSize="15" Text="Pump ID:" HorizontalAlignment="Left"/>
<TextBlock Foreground="Black" FontSize="15" Text="{TemplateBinding PumpID}" HorizontalAlignment="Right"/>
<TextBlock Foreground="Black" FontSize="15" Text="Fuel Type:" HorizontalAlignment="Left"/>
<TextBlock Foreground="Black" FontSize="15" Text="{TemplateBinding FuelType}" HorizontalAlignment="Right"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
and the calling window
<Window x:Class="POS.Window1"
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:controls="clr-namespace:POS"
mc:Ignorable="d"
Title="Window1" Height="300" Width="300">
<Grid>
<controls:PermissionBox PumpID="100" FuelType="Unleaded"> </controls:PermissionBox>
</Grid>
</Window>
Really appreciate any help with resolving this.
Related
I am rewriting import masks that have a lot in common, so I want (and must) use inheritance.
I have a basic UserControl with all common controls: (I have left out the grid definitions)
BaseClass.xaml
<UserControl x:Class="BaseImport.BaseClass"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<UserControl.Template>
<ControlTemplate TargetType="UserControl">
<Grid>
<Border Grid.Row="0" Grid.Column="0">
<StackPanel>
<Label Content="Text1:"/>
<ComboBox Name="cbText1" MinWidth="80"/>
</StackPanel>
</Border>
<Border Grid.Row="0" Grid.Column="1">
<StackPanel>
<Label Content="Text2:"/>
<ComboBox Name="cbText2" MinWidth="80"/>
</StackPanel>
</Border>
<Border Grid.Row="0" Grid.Column="2">
<StackPanel>
<ContentPresenter ContentSource="Content"/> <!-- ContentSource="Content" is the default-->
</StackPanel>
</Border>
<!-- next Row -->
<Border Grid.Row="1" Grid.Column="0">
<StackPanel>
<Label Content="Text3:"/>
<TextBox Name="tbText3" TextWrapping="Wrap" Text="" MinWidth="80" VerticalAlignment="Center"/>
</StackPanel>
</Border>
<Border Grid.Row="1" Grid.Column="1">
<StackPanel>
<ContentPresenter/>
</StackPanel>
</Border>
</Grid>
</ControlTemplate>
</UserControl.Template>
</UserControl>
This is a kind of Template that gets "used" like this:
MainWindow.xaml (just for demonstration a mainwindow)
<Window x:Class="zzz.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:my="clr-namespace:BaseImport;assembly=BaseImport"
mc:Ignorable="d"
Title="MainWindow" Height="280" Width="600">
<my:BaseClass>
<StackPanel>
<Label Content="Test:"/>
<ComboBox ItemsSource="{Binding TestTyps}" MinWidth="80"/>
</StackPanel>
</my:BaseClass>
</Window>
MainWindow.xaml.cs
using WpfApp1.ViewModel;
namespace zzz
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new MainViewModel();
}
}
}
and to wrap it up MainViewModel.cs:
namespace WpfApp1.ViewModel
{
public class MainViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;
public string[] TestTyps { get { return new string[] { "abc", "123", "xyz" }; } }
}
}
If I have one ContentPresenter everything works fine. But in the BaseClass I have two, potentially more.
Like this only the "last" Presenter gets populated. And in MainWindow.xaml can only be one declared.
How can I put more Content in MainWindow.xaml?
How can I select the right one?
Thanks
The red rectangle is were the second presenter is located (row 1, column 1) but I want it to be were the arrow points (row 0, column 2).
I want another control in place of the red rectangle also declared in MainWindow.xaml.
The stuff you put directly in the <my:BaseClass> tags is the contentProperty which can be only one.
Why only the last ContentPresenter shows it? Because each VisualElement can only have one parent, so the last one claiming it wins.
However you can create as many properties as you want.
<UserControl x:Class="BaseImport.BaseClass"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<UserControl.Template>
<ControlTemplate TargetType="UserControl">
<Grid>
<Border Grid.Row="0" Grid.Column="0">
<StackPanel>
<Label Content="Text1:"/>
<ComboBox Name="cbText1" MinWidth="80"/>
</StackPanel>
</Border>
<Border Grid.Row="0" Grid.Column="1">
<StackPanel>
<Label Content="Text2:"/>
<ComboBox Name="cbText2" MinWidth="80"/>
</StackPanel>
</Border>
<Border Grid.Row="0" Grid.Column="2">
<StackPanel>
<ContentPresenter ContentSource="FirstContent"/>
</StackPanel>
</Border>
<!-- next Row -->
<Border Grid.Row="1" Grid.Column="0">
<StackPanel>
<Label Content="Text3:"/>
<TextBox Name="tbText3" TextWrapping="Wrap" Text="" MinWidth="80" VerticalAlignment="Center"/>
</StackPanel>
</Border>
<Border Grid.Row="1" Grid.Column="1">
<StackPanel>
<ContentPresenter ContentSource="SecondContent"/>
</StackPanel>
</Border>
</Grid>
</ControlTemplate>
</UserControl.Template>
</UserControl>
<my:BaseClass>
<my:BaseClass.FirstContent>
<StackPanel>
<Label Content="Test:"/>
<ComboBox ItemsSource="{Binding TestTyps}" MinWidth="80"/>
</StackPanel>
<my:BaseClass.FirstContent>
<my:BaseClass.SecondContent>
<StackPanel>
<Label Content="Test10:"/>
<TextBox Text="{Binding Whatever}" MinWidth="80"/>
</StackPanel>
<my:BaseClass.SecondContent>
</my:BaseClass>
I gave the point to Firo, because he gave me the right answer. But I want to give some final thoughts, so everyone can benefit from it.
For the xaml "base-class":
<UserControl
x:Class="BaseImport.BaseClass"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<UserControl.Template>
<ControlTemplate TargetType="UserControl">
<Grid>
<Border Grid.Row="0" Grid.Column="0">
<StackPanel>
<Label Content="Text1:"/>
<ComboBox Name="cbText1" MinWidth="80"/>
</StackPanel>
</Border>
<Border Grid.Row="0" Grid.Column="1">
<StackPanel>
<Label Content="DB:"/>
<ComboBox
Name="cbxDB"
ItemsSource="{Binding DBs}"
SelectedItem="{Binding DB}"
MinWidth="80"
SelectionChanged="selectionChanged"
Loaded="DB_Loaded">
<!--
here the DataContext can be set differently
<ComboBox.DataContext>
<Views:DBViewModel/>
</ComboBox.DataContext>
-->
</ComboBox>
</StackPanel>
</Border>
<!-- the content placeholder -->
<Border Grid.Row="0" Grid.Column="2">
<StackPanel MinWidth="80">
<ContentControl
Content="{Binding ContentOne}"
ContentTemplate="{Binding ContentOneTemplate}"
ContentTemplateSelector="{Binding ContentOneTemplateSelector}"/>
</StackPanel>
</Border>
<Border Grid.Row="0" Grid.Column="3">
<StackPanel MinWidth="80">
<ContentControl
Content="{Binding ContentTwo}"
ContentTemplate="{Binding ContentTwoTemplate}"
ContentTemplateSelector="{Binding ContentTwoTemplateSelector}"/>
</StackPanel>
</Border>
</Grid>
</ControlTemplate>
</UserControl.Template>
</UserControl>
For the "base-class":
public partial class BaseClass : UserControl
{
public BaseClass(BaseViewModel _bvm)
{
InitializeComponent();
// set DataContext for all other controls
DataContext = _bvm;
}
#region DependencyProperty
public static readonly DependencyProperty ContentOneProperty = DependencyProperty.Register("ContentOne", typeof(object), typeof(BaseImportClass));
public static readonly DependencyProperty ContentOneTemplateProperty = DependencyProperty.Register("ContentOneTemplate", typeof(DataTemplate), typeof(BaseImportClass));
public static readonly DependencyProperty ContentOneTemplateSelectorProperty = DependencyProperty.Register("ContentOneTemplateSelector", typeof(DataTemplateSelector), typeof(BaseImportClass));
public static readonly DependencyProperty ContentTwoProperty = DependencyProperty.Register("ContentTwo", typeof(object), typeof(BaseImportClass));
public static readonly DependencyProperty ContentTwoTemplateProperty = DependencyProperty.Register("ContentTwoTemplate", typeof(DataTemplate), typeof(BaseImportClass));
public static readonly DependencyProperty ContentTwoTemplateSelectorProperty = DependencyProperty.Register("ContentTwoTemplateSelector", typeof(DataTemplateSelector), typeof(BaseImportClass));
public object ContentOne
{
get { return GetValue(ContentOneProperty); }
set { SetValue(ContentOneProperty, value); }
}
public DataTemplate ContentOneTemplate
{
get { return (DataTemplate)GetValue(ContentOneTemplateProperty); }
set { SetValue(ContentOneTemplateProperty, value); }
}
public DataTemplateSelector ContentOneTemplateSelector
{
get { return (DataTemplateSelector)GetValue(ContentOneTemplateSelectorProperty); }
set { SetValue(ContentOneTemplateSelectorProperty, value); }
}
public object ContentTwo
{
get { return GetValue(ContentTwoProperty); }
set { SetValue(ContentTwoProperty, value); }
}
public DataTemplate ContentTwoTemplate
{
get { return (DataTemplate)GetValue(ContentTwoTemplateProperty); }
set { SetValue(ContentTwoTemplateProperty, value); }
}
public DataTemplateSelector ContentTwoTemplateSelector
{
get { return (DataTemplateSelector)GetValue(ContentTwoTemplateSelectorProperty); }
set { SetValue(ContentTwoTemplateSelectorProperty, value); }
}
#endregion
}
For the "derived" class xaml:
<UserControl x:Class="WpfApp1.DerivedClass"
xmlns:base="clr-namespace:BaseImport.Views;assembly=BaseImport"
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">
<StackPanel>
<base:BaseClass Name="bc">
<base:BaseClass.ContentOneTemplate>
<DataTemplate>
<StackPanel>
<Label Content="Test:"/>
<ComboBox
ItemsSource="{Binding TestTyps}"
SelectedItem="{Binding TestTyp}"
SelectionChanged="TestTypChanged"
Loaded="TestTypLoaded">
<!--
here the DataContext can be set differently
<ComboBox.DataContext>
<Views:ViewModelXYZ/>
</ComboBox.DataContext>
-->
</ComboBox>
</StackPanel>
</DataTemplate>
</base:BaseClass.ContentOneTemplate>
</base:BaseClass>
</StackPanel>
</UserControl>
For the "derived" class:
public partial class DerivedClass : UserControl
{
ViewModelABC vmabc = null;
public DerivedClass(ViewModel _vm) : this()
{
vmabc = _vm;
this.DataContext = vmabc;
bc.DataContext = vmabc; // or another viewmodel that holds TestTyps and TestTyp or leave it for ViewModelXYZ
}
private void TestTypChanged(object sender, SelectionChangedEventArgs e)
{
PropertyChanged();
}
private void TestTypLoaded(object sender, RoutedEventArgs e)
{
(sender as ComboBox).SelectedValue = (this.DataContext as ViewModel(XYZ/ABC).TestTyp;
}
}
That is what I came up.
I hope that helps others.
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 am finding that I am creating the same Button style for multiple buttons but only changing one part - the image that is used on the Button. An example;
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Image Source="pack://application:,,,/Resources/MainWindowIcons/Staff.ico" Height="20"/>
<TextBlock Style="{StaticResource HoverUnderlineStyle}" Text="Staff" Margin="5,0,0,0"/>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
This is the code for the staff Button. If I wanted to add another button I'd replicate the whole style but just change the Source of the Image.
Is there a way I can have on style and then set this on the Button itself - meaning that I don't have to replicate the style multiple times?
You could implement two attached properties - one for the Image source and one for the text - that you can set on any Button:
public class ButtonProperties
{
public static readonly DependencyProperty ImageSourceProperty =
DependencyProperty.RegisterAttached("ImageSource", typeof(Uri), typeof(ButtonProperties));
public static Uri GetImageSource(Button button)
{
return (Uri)button.GetValue(ImageSourceProperty);
}
public static void SetImageSource(Button button, Uri value)
{
button.SetValue(ImageSourceProperty, value);
}
public static readonly DependencyProperty TextProperty =
DependencyProperty.RegisterAttached("Text", typeof(Uri), typeof(ButtonProperties));
public static string GetText(Button button)
{
return (string)button.GetValue(ImageSourceProperty);
}
public static void SetText(Button button, string value)
{
button.SetValue(ImageSourceProperty, value);
}
}
Then you only need to define the ContentTemplate once as a resource, for example in your App.xaml:
<Application x:Class="WpfApplication1.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
StartupUri="MainWindow.xaml">
<Application.Resources>
<DataTemplate x:Key="dataTemplate">
<StackPanel Orientation="Horizontal">
<Image Source="{Binding Path=(local:ButtonProperties.ImageSource), RelativeSource={RelativeSource AncestorType=Button}}" Height="20"/>
<TextBlock Text="{Binding Path=(local:ButtonProperties.Text), RelativeSource={RelativeSource AncestorType=Button}}" Margin="5,0,0,0"/>
</StackPanel>
</DataTemplate>
</Application.Resources>
</Application>
Usage:
<Button local:ButtonProperties.Text="Staff"
local:ButtonProperties.ImageSource="pack://application:,,,/Resources/MainWindowIcons/Staff.ico"
ContentTemplate="{StaticResource dataTemplate}" />
I'm working on a modal dialog popup (I'm not sure about the exact UX term) that is displayed inline, inside of a control or window with darkened background.
Visual example
What I tried is putting a <ContentPresenter /> inside the XAML of the popup and then just instantiate it like this:
<local:Popup Grid.RowSpan="2">
<TextBlock Text="Popup test..." />
</local:Popup>
However, the XAML replaces the entire Popup XAML instead of being placed where the ContentPresenter is.
Q: How is the ContentPresenter here used properly?
Popup.xaml
<ContentControl
x:Class="[...].Popup"
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:[...]"
mc:Ignorable="d" d:DesignWidth="300" d:DesignHeight="300">
<Grid Background="#7f000000">
<Grid Background="White" HorizontalAlignment="Center" VerticalAlignment="Center">
<StackPanel Margin="20">
<TextBlock Text="{Binding Title, RelativeSource={RelativeSource AncestorType=UserControl}}" FontSize="20" />
<ContentPresenter />
</StackPanel>
</Grid>
</Grid>
</ContentControl>
Popup.xaml.cs
using System.Windows;
namespace [...]
{
public partial class Popup : ContentControlBase
{
public static DependencyProperty TitleProperty = DependencyProperty.Register(nameof(Title), typeof(string), typeof(Popup));
public string Title
{
get
{
return (string)GetValue(TitleProperty);
}
set
{
SetValue(TitleProperty, value);
}
}
public Popup()
{
InitializeComponent();
}
}
}
The content of your Popup should be defined as a ControlTemplate for the ContentPresenter to work as expected here. Please refer to the following sample code.
Popup.xaml:
<ContentControl
x:Class="WpfApplication1.Popup"
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:WpfApplication1"
mc:Ignorable="d" d:DesignWidth="300" d:DesignHeight="300"
x:Name="popup">
<ContentControl.Template>
<ControlTemplate TargetType="local:Popup">
<Grid Background="#7f000000">
<Grid Background="White" HorizontalAlignment="Center" VerticalAlignment="Center">
<StackPanel Margin="20">
<TextBlock Text="{Binding Title, ElementName=popup}" FontSize="20" />
<ContentPresenter />
</StackPanel>
</Grid>
</Grid>
</ControlTemplate>
</ContentControl.Template>
Popup1.xaml.cs.
public partial class Popup : ContentControl
{
public static DependencyProperty TitleProperty = DependencyProperty.Register(nameof(Title), typeof(string), typeof(Popup));
public string Title
{
get
{
return (string)GetValue(TitleProperty);
}
set
{
SetValue(TitleProperty, value);
}
}
public Popup()
{
InitializeComponent();
}
}
}
Window1.xaml:
<local:Popup Title="Title...">
<TextBlock>Text...</TextBlock>
</local:Popup>
I'm trying to create a UserControl which is a legend of a graph item,
I've defined it so it has a label with the graph name, a check box which
define whether we show it or not and a rectangle with the graph color.
The xaml is defined like this:
<UserControl x:Class="Resources.LegendItem"
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"
mc:Ignorable="d">
<UserControl.Template>
<ControlTemplate>
<StackPanel Margin="1,0,0,0" Orientation="Horizontal">
<CheckBox Name="cb" IsChecked="{TemplateBinding IsGraphVisible}" >
<StackPanel Margin="1,0,0,0" Orientation="Horizontal">
<Rectangle Name="rec" RadiusX="2" RadiusY="2" Height="10" Width="10" />
<Label Name="lab" Content="{TemplateBinding GraphName}" />
</StackPanel>
</CheckBox>
</StackPanel>
</ControlTemplate>
</UserControl.Template>
</UserControl>
and the cs file is:
namespace Resources
{
public partial class LegendItem : UserControl
{
public static readonly DependencyProperty IsGraphVisibleProperty = DependencyProperty.Register("IsGraphVisible", typeof(Boolean), typeof(LegendItem));
public static readonly DependencyProperty GraphNameProperty = DependencyProperty.Register("GraphName", typeof(String), typeof(LegendItem));
public bool IsGraphVisible
{
get { return (bool)GetValue(IsGraphVisibleProperty); }
set { SetValue(IsGraphVisibleProperty, value); }
}
public string GraphName
{
get { return (string)GetValue(GraphNameProperty); }
set { SetValue(GraphNameProperty, value); }
}
public LegendItem()
{
InitializeComponent();
}
}
}
But when I compile it, I get an error "Cannot find the static member 'IsGraphVisibleProperty' on the type 'Control'."
Any help would be appreciated.
You do not need a template at all. UserControl allows the XAML to be declared directly. You can't set the template in a UserControl:
<UserControl x:Class="Resources.LegendItem" x:Name="MyControl"
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"
mc:Ignorable="d">
<StackPanel Margin="1,0,0,0" Orientation="Horizontal">
<CheckBox Name="cb" IsChecked="{Binding ElementName=MyControl, Path=IsGraphVisible}" >
<StackPanel Margin="1,0,0,0" Orientation="Horizontal">
<Rectangle Name="rec" RadiusX="2" RadiusY="2" Height="10" Width="10" />
<Label Name="lab" Content="{Binding ElementName=MyControl, Path=GraphName}" />
</StackPanel>
</CheckBox>
</StackPanel>
</UserControl>
You need to specify TargetType="{x:Type Resources.LegendItem}" on your ControlTemplate, otherwise it defaults to being a template for Control and you get that error.