Could this custom WPF TextBox control be replaced with an Attached Property? - wpf

I have a simple control that extends the WPF TextBox control. The basic idea is to save space in the UI, by enabling the TextBox to display its own label inside it when it is empty.
I have declared one DependencyProperty called Label in it and have set the TextBox.Template property to a ControlTemplate. The ControlTemplate is the default Windows Aero XAML with the addition of a TextBlock placed behind the control that displays the value of the TextBox.Text property. Its Visibility property is bound with a Converter to be visible whenever the Text property is empty. (There's a lot and mostly uninteresting, so please excuse the scrollbar).
<ControlTemplate x:Key="LabelTextBoxTemplate" TargetType="{x:Type TextBoxBase}" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:Aero="clr-namespace:Microsoft.Windows.Themes;assembly=PresentationFramework.Aero">
<Aero:ListBoxChrome Background="{TemplateBinding Panel.Background}" BorderBrush="{TemplateBinding Border.BorderBrush}" BorderThickness="{TemplateBinding Border.BorderThickness}" RenderMouseOver="{TemplateBinding UIElement.IsMouseOver}" RenderFocused="{TemplateBinding UIElement.IsKeyboardFocusWithin}" Name="Border" SnapsToDevicePixels="True">
<Grid>
<TextBlock Text="{Binding Label, ElementName=This}" Visibility="{Binding Text, ElementName=This, Converter={StaticResource StringToVisibilityConverter}}" SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}" FontWeight="Normal" FontStyle="Italic" Foreground="#99000000" Padding="3,0,0,0" />
<ScrollViewer Name="PART_ContentHost" SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}" />
</Grid>
</Aero:ListBoxChrome>
<ControlTemplate.Triggers>
<Trigger Property="UIElement.IsEnabled" Value="False">
<Setter TargetName="Border" Property="Panel.Background" Value="{DynamicResource {x:Static SystemColors.ControlBrushKey}}" />
<Setter Property="TextElement.Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
(The Name property of the control is set to This for the binding)
So anyway, this all works nicely but I was wondering if I could replace this functionality with an Attached Property. I created an Attached Property called Label and added a callback delegate that gets called when the property is set. In this method, I planned to find the ControlTemplate from the Application.Resources and set the TextBox.Template property to it... I couldn't find a way to access the ControlTemplate so this is my first question.
How can I access a ControlTemplate in the Application.Resources in App.xaml from an Attached Property?
The rest of the question is how can I then bind to the Attached Property in the 'ControlTemplate'?
I have tried several things like
Text="{Binding Path=(Attached:TextBoxProperties.Label),
Source={x:Static Attached:TextBoxProperties}, FallbackValue=''}"
but the TextBoxProperties class that contains the Attached Property is not static.
UPDATE >>>
Thanks to H.B. I found that I could access the ControlTemplate using the following code.
ControlTemplate labelControlTemplate =
(ControlTemplate)Application.Current.FindResource("LabelTextBoxTemplate");

You should be able to access the Application.Resources from: Application.Current.Resources.
The binding should probably be this:
{Binding (Attached:TextBoxProperties.Label),
RelativeSource={RelativeSource AncestorType=TextBox}}
It should not matter if the class containing the property is static, the bindng engine will see whether the property is set on the TextBox, it does not care about the declaring class.

Related

Custom button user control style overwritten when content is set

I am currently using Blend and some tutorials I found online to try to make my own button user control. Normally, I would just use a custom style and apply it to the controls I want, but I wrote in some dependency properties as well so I decided to write my own user control.
Something that I can't get my head around is how to set the Content property without overwriting the styling of the button control. I thought it might have been my code, but I started brand new with another button and I have the same thing happen - when the Content property is set, the button simply turns white and has black text in the upper left corner.
Here is the XAML code that Blend generated for me:
<UserControl x:Class="MyUI.Controls.MyButton"
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"
d:DesignHeight="25" d:DesignWidth="100">
<UserControl.Resources>
<Style x:Key="MyButtonStyle" TargetType="{x:Type Button}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Grid>
<Rectangle/>
<ContentPresenter
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
RecognizesAccessKey="True"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsFocused" Value="True"/>
<Trigger Property="IsDefaulted" Value="True"/>
<Trigger Property="IsMouseOver" Value="True"/>
<Trigger Property="IsPressed" Value="True"/>
<Trigger Property="IsEnabled" Value="False"/>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
<Grid>
<Button Content="Button" Height="25" Style="{DynamicResource MyButtonStyle}" Width="100"/>
</Grid>
Now, if I reference the button in my main window like this, it reverts any styling that was done within the user control:
<local:MyButton Content="test" />
What do I need to change in order to make the button accept a different Content tag?
What you need is to connect your UserControl level Content property to your Button level Content property. By default UserControl's Content property is its only child element, which is Grid in your case. You can either create your own Content property or another one with different name. In your UserControl's .cs file:
/// <summary>
/// Interaction logic for MyButton.xaml
/// </summary>
public partial class MyButton : UserControl
{
public new static DependencyProperty ContentProperty =
DependencyProperty.Register("Content", typeof (object),
typeof (MyButton));
public new object Content
{
get { return GetValue(ContentProperty); }
set { SetValue(ContentProperty, value); }
}
public MyButton()
{
InitializeComponent();
}
}
On your UnserControl's Xaml:
<Button Content="{Binding Path=Content, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
Height="25" Style="{DynamicResource MyButtonStyle}" Width="100"/>
Now Button's Content binds to your UserControl level Content property.

How to bind Fill property to a custom property into a controltemplate

I have a button control which its template is stilyzed in an external resource Theme.xaml. Below the controltemplate definition:
<ControlTemplate x:Key="ButtonTemplate" TargetType="{x:Type Button}">
<Grid x:Name="Grid">
<Border x:Name="Background" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="2,2,2,2" CornerRadius="2,2,2,2">
<Border x:Name="Hover" Background="{StaticResource HoverBrush}" CornerRadius="1,1,1,1" Height="Auto" Width="Auto" Opacity="0"/>
</Border>
<StackPanel Orientation="Horizontal" Margin="2,2,2,2">
<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" RecognizesAccessKey="True" />
</StackPanel>
...
Now I added an item which is an ellipse that must be filled with red or green color (as a semaphore) depending on a custom property defined into my usercontrol:
<UserControl.Resources>
<ResourceDictionary Source="Themes/theme.xaml"/>
</UserControl.Resources>
<Grid>
<Button Click="Button_Click"></Button>
<Ellipse x:Name="ellipse1" Width="20" Height="20" Margin="5,40,45,5"></Ellipse>
</Grid>
and in the behind code I have:
private SolidColorBrush ButtonValue_;
public SolidColorBrush ButtonValue {
get { return ButtonValue_; }
set {
ButtonValue_ = value;
}
}
I'm trying to put into the CONTROLTEMPLATE this ellipse item, but i have some problems regarding how to BIND the Fill property of the ellipse with the ButtonValue custom property into the controlTemplate.
Any hints??
Thanks in advance
You can go to several directions:
Implement a custom control, that is your own class derived from an existing control (Button in your case). Add a dependency property (e.g. ButtonValue). Note - dependency property aren't standard .NET property - they have much more. Check out the following sample: http://msdn.microsoft.com/en-us/library/cc295235(v=expression.30).aspx (A custom button), or here: http://wpftutorial.net/HowToCreateACustomControl.html (A simpler sample, but without a property.
Have a data context for the control. Typically the data context is a separate class (a.k.a. the "View Model"), but if you aren't following the mvvm paradigm, it is OK the data context is self. Whatever data context you are using, it must derived from INotifyPropertyChanged, and it must file PropertyChanged event.
(Recommended!) Create a Control Template for CheckBox. When you come to think about it, logically your control is really a button with a binary state. Red/Green in your case, Checked/Unchecked for a CheckBox. So logically, you are looking for a checkbox, but you just want to present it differently.
So in your control template, draw the ellipse, and add a trigger for the IsChecked property:
<ControlTemplate x:Key="ButtonTemplate" TargetType="{x:Type CheckBox}">
<Grid>
... everything else in the control ...
<Ellipse x:Name="ellipse1" Width="20" Height="20" Margin="5,40,45,5" />
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter TargetName="ellipse1" Property="Fill" Value="Red" />
</Trigger>
<Trigger Property="IsChecked" Value="False">
<Setter TargetName="ellipse1" Property="Fill" Value="Green" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
This is a nice example for the difference between behavior and presentation of WPF.
While your control may look like a button, it behaves like a CheckBox, in the sense that it has two states.
EDIT: Use ToggleButton - this is the base class of CheckBox (and RadioButton), and it has exactly the functionality that you need, including the IsChecked property.
You have a couple of options:
1.The easiest one is to re-purpose an unused Brush or Color(with a converter) Button existing property:
<Window.Resources>
<ControlTemplate x:Key="repurposedProperty" TargetType="Button">
<Border Background="{TemplateBinding BorderBrush}">
<ContentPresenter/>
</Border>
</ControlTemplate>
</Window.Resources>
...
<Button Template="{StaticResource repurposedProperty}">Button</Button>
2.Other option is to define an attached property and use it in the ControlTemplate. On any Button that you apply the template to you have to set the attached property:
public static readonly DependencyProperty AttachedBackgroundProperty =
DependencyProperty.RegisterAttached("AttachedBackground", typeof (Brush), typeof (MainWindow),
new PropertyMetadata(default(Brush)));
public static void SetAttachedBackground(UIElement element, Brush value)
{
element.SetValue(AttachedBackgroundProperty, value);
}
public static Brush GetAttachedBackground(UIElement element)
{
return (Brush) element.GetValue(AttachedBackgroundProperty);
}
...
<
Window.Resources>
<ControlTemplate x:Key="attachedProperty" TargetType="Button">
<Border Background="{TemplateBinding WpfApplication1:MainWindow.AttachedBackground}">
<ContentPresenter/>
</Border>
</ControlTemplate>
</Window.Resources>
...
<Button Template="{StaticResource attachedProperty}">
<WpfApplication1:MainWindow.AttachedBackground>
<SolidColorBrush Color="Pink"></SolidColorBrush>
</WpfApplication1:MainWindow.AttachedBackground>
Button</Button>
PS: you can use a binding to set the value of the attached property.

WPF Visual inheritance

Need an advice (better from your real projects) - what is the best way to do visual inheretence in WPF?
More concrete: How to inherete window view with a statusbar?
There is no way to inherete one xaml file from another. Then, are you create User Control MyStatusbar and paste it on every page?
It is possible to create Style Template for base window and use style inheretence, however this only for simple visual properties (color, size).
Second idea is to create base DataTemplate, but there is no inheritance.
P.S. In WinForms there is base Form with status bar and some logic. After adding property
public string StatusbarText {set{baseStatusbar.Text = value;}}
it is very simple to use the property in child forms. Plus we have view inheritance with status bar.
I know how to inherete logic in WPF, but what to do with visualisation.
You could certainly create a custom Window control that adds a StatusbarText property. Alternatively, you could use a custom Style for Window, the only question there is how to pass the status bar items into your Style. For that you can use attached properties.
If you go this route, you cannot inherit your custom Style from the default one, as you need to completely redefine the ControlTemplate. A Style for Window would look like:
<ControlTemplate x:Key="WindowTemplateKey"
TargetType="{x:Type Window}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Grid>
<AdornerDecorator>
<DockPanel>
<StatusBar DockPanel.Dock="Bottom" ItemsSource="{Binding Tag, RelativeSource={RelativeSource TemplatedParent}}" />
<ContentPresenter/>
</DockPanel>
</AdornerDecorator>
<ResizeGrip x:Name="WindowResizeGrip"
HorizontalAlignment="Right"
VerticalAlignment="Bottom"
Visibility="Collapsed"
IsTabStop="false"/>
</Grid>
</Border>
<ControlTemplate.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="Window.ResizeMode"
Value="CanResizeWithGrip"/>
<Condition Property="Window.WindowState"
Value="Normal"/>
</MultiTrigger.Conditions>
<Setter TargetName="WindowResizeGrip"
Property="Visibility"
Value="Visible"/>
</MultiTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
<Style x:Key="{x:Type Window}"
TargetType="{x:Type Window}">
<Setter Property="Foreground"
Value="{DynamicResource {x:Static SystemColors.WindowTextBrushKey}}"/>
<Setter Property="Background"
Value="{DynamicResource {x:Static SystemColors.WindowBrushKey}}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Window}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<AdornerDecorator>
<DockPanel>
<StatusBar DockPanel.Dock="Bottom" ItemsSource="{Binding Tag, RelativeSource={RelativeSource TemplatedParent}}" />
<ContentPresenter/>
</DockPanel>
</AdornerDecorator>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="Window.ResizeMode"
Value="CanResizeWithGrip">
<Setter Property="Template"
Value="{StaticResource WindowTemplateKey}"/>
</Trigger>
</Style.Triggers>
</Style>
If you use the Style above, you can set the Window.Tag property to be a list of items you want displayed in the StatusBar. The biggest problem with this approach is you would need to add attached properties for things like StatusBar.ItemContainerStyle so you can customize the look of your status bar.
Same holds for if you use a DataTemplate. So i you know you only ever want single text in your StatusBar, you could use the following in the ControlTemplates above and set the Window.Tag to the string (or use an attached property).
<StatusBar DockPanel.Dock="Bottom">
<StatusBarItem Content="{Binding Tag, RelativeSource={RelativeSource TemplatedParent}}" />
</StatusBar>
It's better to use MVVM, look WPF Apps With The Model-View-ViewModel Design Pattern
You can write a base ViewModel with StatusbarText property and then inherit from the base ViewModel.
Then you can use this ViewModel property with data binding in Styles and Templates, look Customize Data Display with Data Binding and WPF
Also look at this question

Attach xaml style to element without explicitly stating it

I have a problem styling/templating an AccordionItem in the accordion control from the silverlight toolkit. For some reason, the child controls are Horizontally Aligned Left. The only way I can get to fix this is to edit the ExpandableContentControlStyle on the AccordionItem.
The style is located below:
<Style x:Key="ExpandableContentControlStyle1" TargetType="layoutPrimitivesToolkit:ExpandableContentControl">
<Setter.Value>
<ControlTemplate TargetType="layoutPrimitivesToolkit:ExpandableContentControl">
<ContentPresenter x:Name="ContentSite" Cursor="{TemplateBinding Cursor}" Margin="0" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" ContentTemplate="{TemplateBinding ContentTemplate}" HorizontalAlignment="Stretch" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Now my problem is that to have this style being attached to the AccordionItem, I have to set it:
<layoutToolkit:Accordion HorizontalAlignment="Stretch">
<layoutToolkit:AccordionItem Header="Hello" BorderBrush="{x:Null}" ExpandableContentControlStyle="{StaticResource ExpandableContentControlStyle1}"/>
<layoutToolkit:AccordionItem Header="Haha" BorderBrush="{x:Null}"/>
</layoutToolkit:Accordion>
But those AccordionItem will be generated from an ItemSource. What I'd like to do is to have that style be applied to the generated AccordionItem without setting it.
PS. The above problem can become obsolete if I can just find out how to edit the (ContentPresenter x:Name="ContentSite") from the parent Accordion. I cannot edit it from none of the following template properties:
ContentTemplate
ItemContainerStyle
AccordionButtonStyle
ItemsPanel
ItemTemplate
If anyone knows what is going on with that, I'd appreciate the help or you can just help with styling of multiple elements.
I haven't used the Accordion control myself, though typically you set the ItemContainerStyle to the style you want for each item in the list. For instance, if you wanted a specific ListBoxItem style on a ListBox, you set the ItemContainerStyle to the ListBoxItem style you want. I glanced at the source for the Accordion and this seems to hold true for that control as well. Try setting the ItemContainerStyle property of the Accordion to your ExpandableContentControlStyle1.
<layoutToolkit:Accordion
HorizontalAlignment="Stretch"
ItemContainerStyle="{StaticResource ExpandableContentControlStyle1}">
</layoutToolkit:Accordion>
To set the style outside of the control itself, create a style for the Accordion. If you're using Silverlight 4, you can use implicit styles. Put the following style in the <UserControl.Resources> section of your page.
<Style TargetType="layoutToolkit:Accordion">
<Setter Property="ItemContainerStyle" Value="{StaticResource ExpandableContentControlStyle1}"/>
</Style>
Otherwise, with Silverlight 3 you'll have to explicitly give the style a Key and explicitly set the style on the Accordion control.
<Style x:Key="Control_Accordion" TargetType="layoutToolkit:Accordion">
<Setter Property="ItemContainerStyle" Value="{StaticResource ExpandableContentControlStyle1}"/>
</Style>
<layoutToolkit:Accordion
Style="{StaticResource Control_Accordion}"
HorizontalAlignment="Stretch">
</layoutToolkit:Accordion>

ItemContainerStyle in Custom Control Derived From ListBox

Please bear with me Silverlight Designer Gurus, this is compicated (to me).
I'm creating a custom control which derives form the Silverlight 3.0 ListBox. In an effort not to show tons of code (initially), let me describe the setup.
I have a class library containing a class for my control logic. Then I have a Themes/generic.xaml that holds the styling details. In generic.xaml, I have a style that defines the default layout and look for the ListBox where I'm setting a values for the Template, ItemsPanel and ItemTemplate.
In my test app, I add my control on to MainPage.xaml and run it and it works great. I dynamically bind data to my control and that works fine.
Now I want to set the ItemContainerStyle for my derived control. If I create a style in the MainPage.xaml file and set the ItemContainerStyle property to that control as in:
<dti:myControl x:Name="MyControl1" ItemContainerStyle="{StaticResource MyListBoxItem}"
Height="500"
Width="200"
Margin="10"
Background="AliceBlue"
/>
It works as expected.
However, I'd like to do this in the class library or, more specifically, in generic.xaml. I tried to this Setter to my current Style:
<Setter Property="ItemContainerStyle">
<Setter.Value>
<ControlTemplate>
<Grid Background="Red" Margin="3">
<ContentPresenter x:Name="contentPresenter"
ContentTemplate="{TemplateBinding ContentTemplate}"
HorizontalAlignment="Stretch" Margin="3"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
And it fails miserably with:
"System.ArgumentException: 'System.Windows.Controls.ControlTemplate' is not a valid value for property 'ItemContainerStyle'."
Note: This is not my actual style I'd like to use for ItemContainerStyle. I'm actually looking to plug in some VSM here for the various selected/unselected states of the a ListBoxItem (for a dynamically bound control).
So, to the question is how do I apply the ItemContainterStyle to my custom control when it's defined using generic.xaml? I do not want that property set when I actually use the control later on.
Thanks,
Beaudetious
You missed to put Style tag inside your Setter.Value. ItemContainerstyle explects a Style to ListBoxItem(Unless you subclassed ListBoxItem to your own derived version.)
<Setter Property="ItemContainerStyle">
<Setter.Value>
<Style TargetType=”{x:Type ListBoxItem}“ >
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Grid Background="Red" Margin="3">
<ContentPresenter x:Name="contentPresenter" ContentTemplate="{TemplateBinding ContentTemplate}" HorizontalAlignment="Stretch" Margin="3"/>
</Grid>
</ControlTemplate>
<Setter.Value>
</Style>
</Setter.Value>

Resources