I have a custom dependency property:
public static readonly DependencyProperty HeaderProperty = DependencyProperty.Register("HeaderProperty", typeof(string), typeof(RadAdjustableSlider));
public string Header
{
get
{
return (string)GetValue(HeaderProperty);
}
set
{
SetValue(HeaderProperty, value);
}
}
I then have a binding in my xaml:
<TextBlock Name="txtHeader" Text="{Binding ElementName=main, Path=Header, UpdateSourceTrigger=PropertyChanged, Mode=OneWay}" />
Note that I also have this in the declaration at the top of the xaml file:
x:Name="main"
Finally, I have this constructor:
public RadAdjustableSlider()
{
InitializeComponent();
this.Header = "Header";
}
When I put this control inside of another parent control, the Header textblock is blank. Why?
Edit: This blog says that the correct way to do this is by providing a ValidateValueCallback in the DependencyProperty.Register call, but that seems like quite a bit of plumbing and doesn't explain the way dependency properties behave when interacting with external controls. Am I really going to have to write callback functions for all of my dependency properties?
There is a HeaderedContentControl and HeaderedItemsControl in the framework already...
But if you really want to create your own then you should probably use a TemplateBinding. Try something like this instead:
class MyHeaderedControl : ContentControl
{
public static readonly DependencyProperty HeaderProperty = DependencyProperty.Register(
"Header",
typeof(object),
typeof(MyHeaderedControl),
new PropertyMetadata());
public MyHeaderedControl()
{
this.DefaultStyleKey = typeof(MyHeaderedControl);
}
}
Then in your project create a file at "\Themes\Generic.xaml". This is a specially named file and must be in the root of the project then in the Themes folder. It must contain a ResourceDictionary.
<ResourceDictionary
xmlns="..."
xmlns:x="..."
xmlns:c="MyControlLibrary1"
>
<Style TargetType="{x:Type c:MyHeaderedControl>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type c:MyHeaderedControl}">
<StackPanel>
<ContentControl Content="{TemplateBinding Header}" />
<ContentPresenter />
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
Also, in your AssemblyInfo.cs add this attribute if it's not there already:
[assembly: ThemeInfo(ResourceDictionaryLocation.SourceAssembly,
ResourceDictionaryLocation.SourceAssembly)]
So for the overview. The general idea is to create some type of logical control where you have properties and events and logic etc. Then in the same assembly you provide default themes. That is how the controls will be displayed by default. At any place where the controls are used the default templates can be overriden and specific templates can be overridden as usual.
So this is the most pain free way you can add custom content like this to your custom controls! Try it once and it will make sense and not feel to cludgy. If you make more controls just keep adding them to the Generic.xaml file.
As justin.m.chase mentioned above, a custom control is probably the best way to go but UserControls are a common scenario so I'll add my 2c anyway.
A UserControl does not set the DataContent property for you and therefore all your bindings inside your UserControl XAML resolve to the DataContent of where you placed the control.
To change this behaviour, either set the DataContext property inside your usercontrol constructor:
public RadAdjustableSlider()
{
InitializeComponent();
this.Header = "Header";
this.DataContext = this;
}
and then bind like this:
<TextBlock Text="{Binding Header}" />
or don't set the DataContext and bind like this:
<TextBlock Text="{Binding Header, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ns:RadAdjustableSlider}}}" />
Related
Read update for a solution
I have a small WPF Application with several small game clones such as Minesweeper, Connect 4, Tic Tac Toe among others.
Common for all of these is that they are all a uniform grid of squares, each square is controlled by a button.
For each of these games I have defined a UserControl with a UniformGrid ItemsPanelTemplate in their XAML.
The only place they differ is the DataTemplate used:
<ItemsControl.ItemTemplate>
<DataTemplate>
<!-- Button that fit the specific need of the game -->
</DataTemplate>
</ItemsControl.ItemTemplate>
To avoid repetition I wanted to create a UserControl which has a DataTemplate dependency property (and has an ItemsControl defined in the XAML named itemscontrol):
public DataTemplate DataTemplate
{
get => (DataTemplate)GetValue(DataTemplateProperty);
set => SetValue(DataTemplateProperty, value);
}
public static readonly DependencyProperty DataTemplateProperty =
DependencyProperty.Register("DataTemplate", typeof(DataTemplate), typeof(BoardGameControl));
public BoardGameControl()
{
InitializeComponent();
itemscontrol.ItemTemplate = DataTemplate;
}
Which I tried to use in my application like so:
<controls:BoardGameControl>
<controls:BoardGameControl.DataTemplate>
<DataTemplate>
<Button Content="Hi"/>
</DataTemplate>
</controls:BoardGameControl.DataTemplate>
</controls:BoardGameControl>
I have tried some other approaches as well but none have worked.
How can I avoid having to define a new ItemsControl for each game and instead have a UserControl or Style that simply accepts a different Button depending on the situation?
Update
I combined both the solution I marked as Accepted and the comment by #Joe on this post.
Instead of a UserControl I created a Custom Control with my desired properties and then styled in in the Generic.xaml file to my liking. I also removed the DataTemplate property from my Custom Control and instead added DataTemplates in my App.xaml for each different VM.
Below you will find the code behind and style of my new Custom Control.
public class GameBoardControl : Control
{
public int Columns
{
get => (int)GetValue(ColumnsProperty);
set => SetValue(ColumnsProperty, value);
}
public static readonly DependencyProperty ColumnsProperty =
DependencyProperty.Register(nameof(Columns), typeof(int), typeof(BoardGameControl));
public int Rows
{
get => (int)GetValue(RowsProperty);
set => SetValue(RowsProperty, value);
}
public static readonly DependencyProperty RowsProperty =
DependencyProperty.Register(nameof(Rows), typeof(int), typeof(BoardGameControl));
static GameBoardControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(GameBoardControl), new FrameworkPropertyMetadata(typeof(GameBoardControl)));
}
}
<Style TargetType="{x:Type controls:GameBoardControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type controls:GameBoardControl}">
<ItemsControl ItemsSource="{Binding Squares}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="{Binding Columns, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type controls:GameBoardControl}}}"
Rows="{Binding Rows, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type controls:GameBoardControl}}}"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Note that I always bind the ItemsSource to Squares, I am able to do this since all of my games have an ObservableCollection called Squares where the Square View Models are stored.
Example of a DataTemplate
<DataTemplate DataType="{x:Type mvvmtoolkit:MemorySquareVM}">
<Button Command="{Binding RelativeSource={RelativeSource AncestorType=controls:GameBoardControl}, Path=DataContext.PressSquareCommand}"
CommandParameter="{Binding}"
Background="{Binding Position, Converter={local:PositionToColorConverter}}"
Style="{StaticResource MemoryButton}"/>
</DataTemplate>
If I understand correctly what you need, then you need to slightly change the implementation: You should not use value assignment in Code Behind - Instead, you need to use binding in XAML.
public BoardGameControl()
{
InitializeComponent();
// itemscontrol.ItemTemplate = DataTemplate;
}
<ItemsControl ItemTemplate="{Binding DataTemplate, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:BoardGameControl}}}">
I also strongly advise in such cases (when properties are added to an element, its behavior is changed, etc.) to use not UserControl, but Custom Control.
why my implementation did not work?
Implementation on Sharp:
public static readonly DependencyProperty DataTemplateProperty =
DependencyProperty.Register("DataTemplate", typeof(DataTemplate), typeof(BoardGameControl),
new PropertyMetadata((d, e) => ((BoardGameControl)d).itemscontrol.ItemTemplate = (IEnumerable) e.NewValue));
public BoardGameControl()
{
InitializeComponent();
// itemscontrol.ItemTemplate = DataTemplate;
}
I would make a Custom Control, however I do not know how to implement the logic of an ItemsControl / UniformGrid myself
If you work in the Studio, then select "Add" -> "Create element" in the context menu. A combo box will appear and select "Custom Control WPF" from it.
You will have a class code derived from Control. Add properties and the logic of behavior you need to it.
And the "Themes/Generic.xaml" file will also be created - this is the default theme. This theme will have a template for your element - edit it as you need.
I want some of my Button, ToggleButton and RadioButton to have a Geometry property so that I can use in ControlTemplates to avoid boilerplate when assigning instance-specific Geometries to those controls.
For example, currently I can write this:
<my:GeometryButton Geometry="{StaticResource OneGeometry}"/>
<my:GeometryButton Geometry="{StaticResource OtherGeometry}"/>
<!-- ...and inside the Style for GeometryButton: -->
<ContentControl TargetType="{x:Type my:GeometryButton}">
<Border>
<Path Data={TemplateBinding Geometry}/>
</Border>
</ContentControl>
With this GeometryButton class:
public class GeometryButton : Button
{
static GeometryButton()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(GeometryButton),
new FrameworkPropertyMetadata(typeof(GeometryButton)));
}
public Geometry Geometry
{
get { return (Geometry)GetValue(GeometryProperty); }
set { SetValue(GeometryProperty, value); }
}
public static readonly DependencyProperty GeometryProperty =
DependencyProperty.Register("Geometry",
typeof(Geometry),
typeof(GeometryButton),
new PropertyMetadata(default(Geometry)));
}
The problem is, if I am to define GeometryToggleButton and GeometryRadioButton classes, I am supposed to repeat the DependencyProperty code in each class, violating DRY.
Also, since RadioButton derives from ToggleButton, and the later and Button in turn derive from ButtonBase, I think I could take advantage of this, but if I need to inherit from each class separately, I don't benefit from inheritance at all.
So I considered to use AttachedProperties, but the tutorials and examples usually mention examples like DockPanel.Dock, Grid.Left, or Control.Foreground, implying the existence of some "Parent", so I am not sure of:
Does the AttachedProperties concept applies to my use case in the first place?
If yes, how am I supposed to implement it?
Create a regular attached property. In your control templates, use it.
No seriously, there's not a lot more to it than that.
For example, I wrote an attached CornerRadius property so that many different control styles could specify a CornerRadius, which would be used by their templates.
public static class Attached
{
public static readonly DependencyProperty CornerRadiusProperty
= DependencyProperty.RegisterAttached(
"CornerRadius",
typeof(CornerRadius),
typeof(Attached),
new FrameworkPropertyMetadata(
new CornerRadius(0),
FrameworkPropertyMetadataOptions.AffectsRender
| FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
PropertyChanged)
);
public static CornerRadius GetCornerRadius(DependencyObject uie)
{
return (CornerRadius)uie.GetValue(CornerRadiusProperty);
}
public static void SetCornerRadius(DependencyObject uie, CornerRadius value)
{
uie.SetValue(CornerRadiusProperty, value);
}
}
XAML
N.B. The parens around (edcorpext:Attached.CornerRadius) in the Binding are critical, so it understands that the string is one indivisible path segment referring to an attached property; otherwise it tries to parse it as a path to a property of Binding.Source, hits the :, and throws an exception.
<ControlTemplate x:Key="EdCorpButtonTemplate" TargetType="{x:Type Button}">
<Grid>
<Border
x:Name="PART_BackgroundBorder"
CornerRadius="{Binding (edcorpext:Attached.CornerRadius), RelativeSource={RelativeSource TemplatedParent}}"
BorderThickness="1.3"
BorderBrush="{StaticResource ControlBorderBrush}"
Background="{StaticResource EdCorpGrayMediumGradientBrush}"
SnapsToDevicePixels="True"
/>
<!-- etc. etc. etc. -->
<Style TargetType="{x:Type Button}" BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="edcorpext:Attached.CornerRadius" Value="{StaticResource ButtonCornerRadius}" />
<Setter Property="Template" Value="{StaticResource EdCorpButtonTemplate}" />
<!-- etc. etc. etc. -->
They told me we needed the UI on this application to look "more modern" and since we don't have a real designer who knows what he's doing, I put asymmetrical rounded corners on stuff. It was actually a lot worse before.
I'm trying to simulate the ASP.Net master-page concept (Layout in MVC) on WPF Windows.
I have a CustomWindow class that specifies some behaviors for this sort of Windows:
public class CustomWindow : Window
{
//...
}
And MasterWindowBase; a CustomWindow that takes some sort of my UserControls to be the window content (via style):
public abstract class MasterWindowBase : CustomWindow
{
public MasterWindowBase(MyUserControlBase content)
{
ContentUserControl = content;
Style = Application.Current.FindResource("MasterWindowStyle") as Style;
}
#region ContentUserControl Property
public MyUserControlBase ContentUserControl
{
get { return (MyUserControlBase)GetValue(ContentUserControlProperty); }
set { SetValue(ContentUserControlProperty, value); }
}
public static readonly DependencyProperty ContentUserControlProperty =
DependencyProperty.Register("ContentUserControl", typeof(MyUserControlBase), typeof(MasterWindowBase));
#endregion
}
The style defined in application resources:
<Style x:Key="MasterWindowStyle" TargetType="{x:Type local:MasterWindowBase}">
<Setter Property="Content">
<Setter.Value>
<Grid>
<StackPanel>
<TextBlock Text="This is a master window"/>
<ContentPresenter Content="{Binding ContentUserControl, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MasterWindowBase}}}"/>
</StackPanel>
</Grid>
</Setter.Value>
</Setter>
</Style>
And MasterWindow; a generic window used to create an instance of MasterWindowBase with the specified UserControl type:
public class MasterWindow<TMyUserControlBase>
: MasterWindowBase
where TMyUserControlBase : MyUserControlBase, new()
{
public MasterWindow() : base(new TMyUserControlBase()) { }
}
Now for the first Window.Show, everything works perfectly, but then I caught two logical errors:
When I close the Window and show a new instance of it with a different UserControl, it loads the content of the first-shown MasterWindow.
When I show a new instance of MasterWindow either with the same UserControl or with a different one without closing the currently-showing window(s), it clears the content of all the currently-showing MasterWindow instances, and loads the content of the first-shown MasterWindow in the new instance.
Note that I can't use the Template property inside the MasterWindowStyle style because the style is actually based on CustomWindow's style (in the real project) which already use the Template property.
Any help will be appreciated.
The reason of observed behavior is you try to set Content of your window in Style. Because it's not a template - WPF will create a tree with your UserControl only once. Then when you apply this style again and again - the same visual tree (with the same, first, UserControl) is reused every time (and of course one control cannot be used in different parents - so it gets removed from where it is hosted now and moved to the window you apply style to).
Long story short - you just should not setting Content via Style in WPF. To fix your immediate problem, you can just set ContentTemplate property instead of Content, and wrap what you have in DataTemplate (leaving everything else the same). This will fix it, because for templates new visual tree is created every time.
Here is yet another way to fix it, still using Content property, however it looks like kind of a hack and I'd better void doing this (though still works):
<Application.Resources>
<Grid x:Shared="False" x:Key="myControl">
<StackPanel>
<TextBlock Text="This is a master window"/>
<ContentPresenter Content="{Binding ContentUserControl, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:MasterWindowBase}}}"/>
</StackPanel>
</Grid>
<Style x:Key="MasterWindowStyle" TargetType="{x:Type local:MasterWindowBase}">
<Setter Property="Content">
<Setter.Value>
<StaticResource ResourceKey="myControl" />
</Setter.Value>
</Setter>
</Style>
</Application.Resources>
Here you define your visual tree in resources with x:Shared="False" attribute. This attribute means every time this resource is referenced - new instance will be created (by default - same instance is reused). Then you reference this resource inside your Style.
The question is not about how to get the stuff working, it already does; it's about some strange behavior I'm experiencing, and I need to understand it. I have a ResourceDictionary that contains some styles, one of them got TargetType="{x:Type UserControl}" and x:Key="UCStyle"; that one is applied on multiple UserControls in the project.
Some of these UserControls got string State property in their ViewModel to be used to apply Visual States (through an external class, and an attached property, bound to the ViewModel in XAML). Till this point everything was perfect, then, I tried to add DependencyProperty State to the UserControl, and simply bind it to the state property in the ViewModel, my attempt was:
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<!--ResourceDictionary Source="..."/-->
</ResourceDictionary.MergedDictionaries>
<Style x:Key="MyStyle" TargetType="{x:Type local:MyUserControl}" BasedOn="{StaticResource UCStyle}">
<Setter Property="State" Value="{Binding State, Mode=TwoWay}"/>
</Style>
</ResourceDictionary>
</UserControl.Resources>
<UserControl.Style>
<DynamicResourceExtension ResourceKey="MyStyle" />
</UserControl.Style>
This worked perfectly at the runtime, but in the design-time, it always underline these lines
And shows an error says:
'MyUserControl' TargetType doesn't match type of element 'UserControl'.
And doesn't apply neither UCStyle nor MyStyle in the XAML Viewer in Visual Studio, and doesn't even draw the child UserControls properly. I didn't expect the solution to run properly, but it did!
Now my questions are:
Why does it show these errors in the design-time while it runs properly?
How to get rid of these errors in the design-time? (I cleaned, and re-built the solution, and restarted Visual Studio, and none of these worked)
What's the best practice to deal with `UserControl` Visual States in such situation in MVVM?
What's the best practice to bind a DependencyProperty of a UserControl to a property in it's ViewModel in MVVM?
I'm using Visual Studio 2012.
The wpf designer is nefarious for showing bogus errors at design time. You can't do much but ignore them.
Visual states are a concern of the UI, and therefore should be contained within the UI. MVVM does not mean no codebehind. Use your codebehind for UI tasks, and put your business logic in your view models.
Your question suggests you're creating custom view models to hold view logic for your user controls. Seriously, don't do that. That'll get you in trouble down the road. It interferes with how databinding is designed to work.
There is no "best practice" for binding user control elements to properties defined on its surface. It depends. Using a style to do this seems odd, however. You can simply give the root of the UserControl an x:Name="root" and then use ElementName=root in your binding.
An example of binding within a UserControl to a property defined on the UserControl (taken from an old prototype)...
Here's a UserControl designed to add or delete a list of stuff.
DependencyProperties defined on the UserControl
Bindings within the UserControl that bind to these properties
I don't guarantee this works, but it will illustrate how it's done:
public partial class ItemsEditor : UserControl
{
#region Items
public static readonly DependencyProperty ItemsProperty =
DependencyProperty.Register(
"Items",
typeof(IEnumerable<Item>),
typeof(ItemsEditor),
new UIPropertyMetadata(null));
public IEnumerable<Item> Items
{
get { return (IEnumerable<Item>)GetValue(ItemsProperty); }
set { SetValue(ItemsProperty, value); }
}
#endregion
#region AddItem
public static readonly DependencyProperty AddItemProperty =
DependencyProperty.Register(
"AddItem",
typeof(ICommand),
typeof(ItemsEditor),
new UIPropertyMetadata(null));
public ICommand AddItem
{
get { return (ICommand)GetValue(AddItemProperty); }
set { SetValue(AddItemProperty, value); }
}
#endregion
#region RemoveItem
public static readonly DependencyProperty RemoveItemProperty =
DependencyProperty.Register(
"RemoveItem",
typeof(ICommand),
typeof(ItemsEditor),
new UIPropertyMetadata(null));
public ICommand RemoveItem
{
get { return (ICommand)GetValue(RemoveItemProperty); }
set { SetValue(RemoveItemProperty, value); }
}
#endregion
public ItemsEditor()
{
InitializeComponent();
}
}
It just lists a bunch of things, you can add a new thing or delete a thing from the list. Here's the bindings in xaml
<UserControl x:Class="LolPrototype.ItemsEditor"
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:t="clr-namespace:UCsAndICommands"
x:Name="root">
<UserControl.Resources>
<DataTemplate DataType="{x:Type t:Item}">
<StackPanel Orientation="Horizontal">
<Button Command="{Binding RemoveItem, ElementName=root}"
CommandParameter="{Binding}">Remove</Button>
<TextBox Text="{Binding Name}" Width="100"/>
</StackPanel>
</DataTemplate>
</UserControl.Resources>
<StackPanel>
<Button Command="{Binding AddItem, ElementName=root}">Add</Button>
<ItemsControl ItemsSource="{Binding Items, ElementName=root}" />
</StackPanel>
</UserControl>
Obviously, you can define DataTemplates outside the list in an ancestor's resources. The point is to show how ElementName bindings can be used to bind against properties defined in the UserControl.
Say I have defined a button with rounded corners.
<Style x:Key="RoundButton" TargetType="Button">
<!-- bla bla -->
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border CornerRadius="0,5,5,0" />
<!-- bla bla -->
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
I it possible that the user of this button can specify the CornerRadius? Can I use a TemplateBinding? But where should I bind to? (to Tag?)
In addition to Kent's suggestions, you could also create an attached property to define the CornerRadius on the button, and bind to that property in the template
In order to use a TemplateBinding, there must be a property on the templated control (Button, in this case). Button does not have a CornerRadius or equivalent property, so your options are:
hard code the value in the template
Hijack another property (such as Tag) to store this information. This is quicker, but lacks type safety, is harder to maintain, and prevents other uses of that property.
Subclass Button and add the propery you need, then provide a template for that subclass. This takes a little longer but yields a much nicer experience for consumers of your control.
The button type doesn't have a property for CornerRadius, so templating this won't be possible. I think the easiest way is creating a new class which inherits from Button and add a new dependency property for the CornerRadius. Like this:
using System.Windows;
using System.Windows.Controls;
namespace WpfApplication3
{
public class RoundedButton:Button
{
public CornerRadius CornerRadius
{
get { return (CornerRadius) GetValue(CornerRadiusProperty); }
set { SetValue(CornerRadiusProperty, value); }
}
public static readonly DependencyProperty CornerRadiusProperty =
DependencyProperty.Register("CornerRadius", typeof (CornerRadius),
typeof (RoundedButton), new UIPropertyMetadata());
}
}
In xaml you can use it like:
<Local:RoundedButton
Style="{DynamicResource RoundButton}"
Width="64" Height="32"
Content="Hello"
CornerRadius="1,5,10,5"
Background="#FF9CFFD5" />
A template binding to the CornerRadius will work without a problem now.