Related
Thanks for #Justin XL and #grek40 help me so much.
I must apologize for my poor English that troubles everyone so much.
And I think I need to improve this question to help any others in the furture.
Here is the newest:
I need to make a square button like this:
My programme is a fullscreen programme that different device has different window's size.
So my square button should be can resizeable also beaucase I want to make a Reactive UI.
And now how can I make a square button?
Thank you.
It's perfectly fine to have pure UI logic like this live inside its code-behind. I'd even argue it's more efficient in most cases.
In your example, it's super easy to square your Rectangle with the following code
XAML
<Border x:Name="MyBorder"
Grid.Column="1"
Grid.Row="1"
SizeChanged="MyBorder_SizeChanged">
<Rectangle x:Name="MyRectangle"
Fill="LightBlue" />
</Border>
Code-behind
private void MyBorder_SizeChanged(object sender, SizeChangedEventArgs e)
{
if (MyBorder.ActualWidth > MyBorder.ActualHeight)
{
MyRectangle.Width = MyRectangle.Height = MyBorder.ActualHeight;
}
else if (MyBorder.ActualWidth < MyBorder.ActualHeight)
{
MyRectangle.Height = MyRectangle.Height = MyBorder.ActualWidth;
}
}
But can we improve this? Since you want a square Button, it makes most sense to create a SquareButton and insert it straight into your Grid.
So the XAML can be simplified to a much more readable version below
<local:SquareButton Grid.Column="1" Grid.Row="1" />
Then you just need to implement the custom control like the following
SquareButton class
[TemplatePart(Name = PART_Root, Type = typeof(Border))]
[TemplatePart(Name = PART_ContentHost, Type = typeof(Border))]
public sealed class SquareButton : Button
{
private const string PART_Root = "Root";
private const string PART_ContentHost = "ContentHost";
public SquareButton()
{
DefaultStyleKey = typeof(SquareButton);
}
protected override void OnApplyTemplate()
{
base.OnApplyTemplate();
var root = (Border)GetTemplateChild(PART_Root);
var contentHost = (Border)GetTemplateChild(PART_ContentHost);
root.SizeChanged += (s, e) =>
{
if (root.ActualWidth > root.ActualHeight)
{
contentHost.Width = contentHost.Height = root.ActualHeight;
}
else if (root.ActualWidth < root.ActualHeight)
{
contentHost.Height = contentHost.Height = root.ActualWidth;
}
};
}
}
SquareButton Style inside Themes/Generic.xaml
<Style TargetType="local:SquareButton">
<Setter Property="Background"
Value="{ThemeResource SystemControlBackgroundBaseLowBrush}" />
<Setter Property="Foreground"
Value="{ThemeResource SystemControlForegroundBaseHighBrush}" />
<Setter Property="BorderBrush"
Value="{ThemeResource SystemControlForegroundTransparentBrush}" />
<Setter Property="BorderThickness"
Value="{ThemeResource ButtonBorderThemeThickness}" />
<Setter Property="Padding"
Value="8,4,8,4" />
<Setter Property="HorizontalAlignment"
Value="Stretch" />
<Setter Property="VerticalAlignment"
Value="Stretch" />
<Setter Property="FontFamily"
Value="{ThemeResource ContentControlThemeFontFamily}" />
<Setter Property="FontWeight"
Value="Normal" />
<Setter Property="FontSize"
Value="{ThemeResource ControlContentThemeFontSize}" />
<Setter Property="UseSystemFocusVisuals"
Value="True" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:SquareButton">
<Border x:Name="Root">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal">
<Storyboard>
<PointerUpThemeAnimation Storyboard.TargetName="ContentHost" />
</Storyboard>
</VisualState>
<VisualState x:Name="PointerOver">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter"
Storyboard.TargetProperty="BorderBrush">
<DiscreteObjectKeyFrame KeyTime="0"
Value="{ThemeResource SystemControlHighlightBaseMediumLowBrush}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter"
Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0"
Value="{ThemeResource SystemControlHighlightBaseHighBrush}" />
</ObjectAnimationUsingKeyFrames>
<PointerUpThemeAnimation Storyboard.TargetName="ContentHost" />
</Storyboard>
</VisualState>
<VisualState x:Name="Pressed">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentHost"
Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0"
Value="{ThemeResource SystemControlBackgroundBaseMediumLowBrush}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter"
Storyboard.TargetProperty="BorderBrush">
<DiscreteObjectKeyFrame KeyTime="0"
Value="{ThemeResource SystemControlHighlightTransparentBrush}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter"
Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0"
Value="{ThemeResource SystemControlHighlightBaseHighBrush}" />
</ObjectAnimationUsingKeyFrames>
<PointerDownThemeAnimation Storyboard.TargetName="ContentHost" />
</Storyboard>
</VisualState>
<VisualState x:Name="Disabled">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentHost"
Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0"
Value="{ThemeResource SystemControlBackgroundBaseLowBrush}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter"
Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0"
Value="{ThemeResource SystemControlDisabledBaseMediumLowBrush}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter"
Storyboard.TargetProperty="BorderBrush">
<DiscreteObjectKeyFrame KeyTime="0"
Value="{ThemeResource SystemControlDisabledTransparentBrush}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Border x:Name="ContentHost" Background="{TemplateBinding Background}">
<ContentPresenter x:Name="ContentPresenter"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Content="{TemplateBinding Content}"
ContentTransitions="{TemplateBinding ContentTransitions}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Padding="{TemplateBinding Padding}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
AutomationProperties.AccessibilityView="Raw" />
</Border>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Hope this helps!
Use the Rectangle.Stretch property:
<Rectangle Fill="Red" Stretch="Uniform"></Rectangle>
I think this answers the actual question of creating a rectangle where width and height are the same and the rectangle is stretched to the available space.
In terms of binding, a MultiBinding on both Width and Height with an IMultiValueConverter implementation that returns the minimum of all input values might work. However, it's only needed for controls that don't provide automated stretching.
You can use attached properties to set the same width/height for a given limit:
public static class SquareSize
{
public static double GetWidthLimit(DependencyObject obj)
{
return (double)obj.GetValue(WidthLimitProperty);
}
public static void SetWidthLimit(DependencyObject obj, double value)
{
obj.SetValue(WidthLimitProperty, value);
}
public static readonly DependencyProperty WidthLimitProperty = DependencyProperty.RegisterAttached(
"WidthLimit", typeof(double), typeof(SquareSize),
new FrameworkPropertyMetadata(double.PositiveInfinity, new PropertyChangedCallback(OnWidthLimitChanged)));
private static void OnWidthLimitChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
UpdateSize(d, (double)e.NewValue, GetHeightLimit(d));
}
public static double GetHeightLimit(DependencyObject obj)
{
return (double)obj.GetValue(HeightLimitProperty);
}
public static void SetHeightLimit(DependencyObject obj, double value)
{
obj.SetValue(HeightLimitProperty, value);
}
public static readonly DependencyProperty HeightLimitProperty = DependencyProperty.RegisterAttached(
"HeightLimit", typeof(double), typeof(SquareSize),
new FrameworkPropertyMetadata(double.PositiveInfinity, new PropertyChangedCallback(OnHeightLimitChanged)));
private static void OnHeightLimitChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
UpdateSize(d, GetWidthLimit(d), (double)e.NewValue);
}
private static void UpdateSize(DependencyObject d, double widthLimit, double heightLimit)
{
double resultSize = Math.Min(widthLimit, heightLimit);
d.SetCurrentValue(FrameworkElement.WidthProperty, resultSize);
d.SetCurrentValue(FrameworkElement.HeightProperty, resultSize);
}
}
Use with appropriate xmlns namespace
<Border x:Name="border" Grid.Column="1" Grid.Row="1">
<Rectangle
Fill="Red"
local:SquareSize.WidthLimit="{Binding ElementName=border,Path=ActualWidth}"
local:SquareSize.HeightLimit="{Binding ElementName=border,Path=ActualHeight}"/>
</Border>
A solution involving a custom control as wrapper for square-spaced content:
public class SquareContentControl : ContentControl
{
protected override Size ArrangeOverride(Size arrangeBounds)
{
var sizeLimit = Math.Min(arrangeBounds.Width, arrangeBounds.Height);
if (VisualChildrenCount > 0)
{
var child = GetVisualChild(0) as UIElement;
if (child != null)
{
child.Arrange(new Rect(new Point((arrangeBounds.Width - sizeLimit) / 2, (arrangeBounds.Height - sizeLimit) / 2), new Size(sizeLimit, sizeLimit)));
return arrangeBounds;
}
}
return base.ArrangeOverride(arrangeBounds);
}
protected override Size MeasureOverride(Size constraint)
{
var sizeLimit = Math.Min(constraint.Width, constraint.Height);
if (VisualChildrenCount > 0)
{
var child = GetVisualChild(0) as UIElement;
if (child != null)
{
child.Measure(new Size(sizeLimit, sizeLimit));
return child.DesiredSize;
}
}
return base.MeasureOverride(constraint);
}
}
Usage:
<Border x:Name="border" Grid.Column="1" Grid.Row="1">
<local:SquareContentControl>
<Rectangle Fill="Red"/>
</local:SquareContentControl>
</Border>
EDIT 2017/8/17 only works on WPF, not UWP.
Using Minimum Converter:
public class MinConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
double result = double.NaN;
if (values != null)
{
try
{
result = values.Cast<double>().Aggregate(double.PositiveInfinity, (a, b) => Math.Min(a, b));
}
catch (Exception)
{
result = double.NaN;
}
}
return result;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
Then in your xaml set the Rectangle Height to match parent's Border Min(ActualHeight, ActualWidth). And the Rectangle Width can just bind to Rectangle's ActualHeight
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="0.1*"></RowDefinition>
<RowDefinition Height="0.8*"></RowDefinition>
<RowDefinition Height="0.1*"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.1*"></ColumnDefinition>
<ColumnDefinition Width="0.8*"></ColumnDefinition>
<ColumnDefinition Width="0.1*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Border x:Name="Bd" Grid.Column="1" Grid.Row="1">
<Rectangle x:Name="R"
Width="{Binding Path=ActualHeight, Mode=OneWay, RelativeSource={RelativeSource Self}}">
<Rectangle.Height>
<MultiBinding Converter="converter:MinConverter">
<Binding ElementName="Bd" Path="ActualHeight"/>
<Binding ElementName="Bd" Path="ActualWidth"/>
</MultiBinding>
</Rectangle.Height>
</Rectangle>
</Border>
</Grid>
I have a UserControl which has a button inside it. This button has a personalized style as bellow. I want to create a property in my UserControl that affects the CornerRadius of a border called "Background" which is inside the button template, so that I can make the button corner round when needed.
I tried to create a property in my usercontrol and use OnApplyTemplate event and GetTemplateChild method but didn't work. I found the border, but nothing happens.
public override void OnApplyTemplate()
{
Border border = GetTemplateChild("Background") as Border;
border.CornerRadius = this.CornerRadius;
}
<Style x:Key="ButtonStyle" TargetType="Button">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Foreground" Value="#FF000000"/>
<Setter Property="Padding" Value="0"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Grid>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal"/>
<VisualState x:Name="MouseOver">
<Storyboard>
<DoubleAnimation Duration="0" To="1" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="BackgroundAnimation"/>
</Storyboard>
</VisualState>
<VisualState x:Name="Pressed">
<Storyboard>
<DoubleAnimation Duration="0" To="1" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="BackgroundAnimation"/>
<DoubleAnimation Duration="0" To=".55" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="DisabledVisualElement"/>
<ColorAnimation Duration="0" To="#8CFFFFFF" Storyboard.TargetProperty="(Rectangle.Fill).(SolidColorBrush.Color)" Storyboard.TargetName="BackgroundGradient"/>
</Storyboard>
</VisualState>
<VisualState x:Name="Disabled">
<Storyboard>
<DoubleAnimation Duration="0" To=".55" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="DisabledVisualElement"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="FocusStates">
<VisualState x:Name="Focused"/>
<VisualState x:Name="Unfocused"/>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Border x:Name="Background" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="0" Background="Transparent" />
<Grid Background="{TemplateBinding Background}" Margin="1">
<Border x:Name="BackgroundAnimation" Background="Transparent" Opacity="0"/>
<Rectangle x:Name="BackgroundGradient" Fill="Transparent" />
</Grid>
<ContentPresenter x:Name="contentPresenter" ContentTemplate="{TemplateBinding ContentTemplate}" Content="{TemplateBinding Content}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
<Rectangle x:Name="DisabledVisualElement" Fill="#FFFFFFFF" IsHitTestVisible="false" Opacity="0" RadiusY="0" RadiusX="0"/>
<Rectangle x:Name="FocusVisualElement" IsHitTestVisible="false" Margin="1" Opacity="0" RadiusY="0" RadiusX="0" Stroke="#FF6DBDD1" StrokeThickness="0"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Edit - Solution
I got what I wanted by creating a heritage of Button with a dependency property for CornerRadius, then using TemplateBinding.
Important XAML parts:
<Style x:Key="ButtonStyle" TargetType="myProject:MyButton">
<ControlTemplate TargetType="myProject:MyButton">
<Border x:Name="Background" BorderBrush="{TemplateBinding BorderBrush}" Background="{TemplateBinding Background}" CornerRadius="{TemplateBinding CornerRadius}">
Full XAML and C#
public class MyButton : Button
{
public static readonly DependencyProperty CornerRadiusProperty =
DependencyProperty.Register("CornerRadius", typeof(CornerRadius),
typeof(MyButton), new FrameworkPropertyMetadata(new CornerRadius(0, 0, 0, 0)));
public CornerRadius CornerRadius
{
get { return (CornerRadius)GetValue(CornerRadiusProperty); }
set { SetValue(CornerRadiusProperty, value); }
}
}
<Style x:Key="ButtonStyle" TargetType="myProject:MyButton">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="myProject:MyButton">
<Grid>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal"/>
<VisualState x:Name="MouseOver">
<Storyboard>
<DoubleAnimation Duration="0" To="1" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="BackgroundAnimation"/>
</Storyboard>
</VisualState>
<VisualState x:Name="Pressed">
<Storyboard>
<DoubleAnimation Duration="0" To="1" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="BackgroundAnimation"/>
<DoubleAnimation Duration="0" To=".55" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="DisabledVisualElement"/>
<ColorAnimation Duration="0" To="#8CFFFFFF" Storyboard.TargetProperty="(Rectangle.Fill).(SolidColorBrush.Color)" Storyboard.TargetName="BackgroundGradient"/>
</Storyboard>
</VisualState>
<VisualState x:Name="Disabled">
<Storyboard>
<DoubleAnimation Duration="0" To=".55" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="DisabledVisualElement"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="FocusStates">
<VisualState x:Name="Focused"/>
<VisualState x:Name="Unfocused"/>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Border x:Name="Background" BorderBrush="{TemplateBinding BorderBrush}" Background="{TemplateBinding Background}" CornerRadius="{TemplateBinding CornerRadius}">
<Grid>
<Border x:Name="BackgroundAnimation" Background="Transparent" Opacity="0"/>
<Rectangle x:Name="BackgroundGradient" Fill="Transparent"/>
</Grid>
</Border>
<ContentPresenter x:Name="contentPresenter" ContentTemplate="{TemplateBinding ContentTemplate}" Content="{TemplateBinding Content}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
<Rectangle x:Name="DisabledVisualElement" Fill="#FFFFFFFF" IsHitTestVisible="false" Opacity="0" RadiusY="{Binding ElementName=Background, Path=CornerRadius.TopLeft}" RadiusX="{Binding ElementName=Background, Path=CornerRadius.BottomLeft}"/>
<Rectangle x:Name="FocusVisualElement" IsHitTestVisible="false" Margin="1" Opacity="0" RadiusX="0" RadiusY="0" Stroke="#FF6DBDD1" StrokeThickness="0"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
The best way to do that through Template Binding. WPF best practices say that you need to apply UI changes through appropriate WPF mechanism: XAML. Consequently, we need to change our Style or Template. If it is possible, we should create a flexible template to allow to change UI by changing of style, creation a new Style without modification of the Template.
Open/Closed SOLID principle: Extend and not modify.
Unfortunately, we forgot that CornerRadius it is the attached property of class Border.
So we can create a Template Binding:
<ControlTemplate TargetType="{x:Type Button}"
x:Key="ControlTemplateButtonNormal">
<Border Background="{TemplateBinding Background}"
BorderThickness="{TemplateBinding BorderThickness}"
BorderBrush="{TemplateBinding BorderBrush}"
CornerRadius="{TemplateBinding Border.CornerRadius}"
x:Name="BorderRoot">
<Grid>
<ContentPresenter IsTabStop="False"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
TextElement.Foreground="{TemplateBinding Foreground}" />
</Grid>
</Border>
<Trigger Property="Validation.HasError"
Value="True">
<Setter Property="Visibility"
TargetName="ErrorElement"
Value="Visible" />
<Setter Property="BorderBrush"
TargetName="BorderRoot"
Value="Transparent" />
</Trigger>
<Trigger Property="IsReadOnly"
Value="True">
<Setter Property="Background"
TargetName="BorderRoot"
Value="{StaticResource BorderBrushReadonly}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
Now we can use this template to create different Styles.
Normal button:
<Style TargetType="{x:Type Button}"
x:Key="ButtonNormalStyle">
<Setter Property="BorderThickness"
Value="1" />
<Setter Property="Border.CornerRadius"
Value="0" />
<Setter Property="Template"
Value="{StaticResource ControlTemplateButtonNormal}" />
</Style>
Button Round:
<Style TargetType="{x:Type Button}"
x:Key="ButtonRoundStyle">
<Setter Property="BorderThickness"
Value="1" />
<Setter Property="Border.CornerRadius"
Value="5,5,5,5" />
<Setter Property="Template"
Value="{StaticResource ControlTemplateButtonNormal}" />
</Style>
I hope it will be useful.
If you have a property called CornerRadius in your control, I'm pretty sure this would do it:
<Border CornerRadius="{TemplateBinding CornerRadius}"/>
You need to search the VisualTree for the border. it's not part of the UserControl's template so it's not a Template child.
Here's some good extensions to help you with this and other situations where you need to traverse the VisualTree :
public static class VisualTreeHelperExtensions
{
public static T FindVisualParent<T>(DependencyObject depObj) where T : DependencyObject
{
var parent = VisualTreeHelper.GetParent(depObj);
if (parent == null || parent is T)
return (T)parent;
return FindVisualParent<T>(parent);
}
public static T FindVisualChild<T>(DependencyObject depObj) where T : Visual
{
if (depObj != null && IsVisual(depObj))
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(depObj, i);
if (child != null && child is T)
{
return (T)child;
}
foreach (T childOfChild in FindVisualChildren<T>(child))
{
return childOfChild;
}
}
}
return null;
}
public static T FindVisualChild<T>(DependencyObject depObj, string name) where T : FrameworkElement
{
if (depObj != null && IsVisual(depObj))
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(depObj, i);
if (child != null && child is T && (child as T).Name.Equals(name))
{
return (T)child;
}
foreach (T childOfChild in FindVisualChildren<T>(child))
{
if (childOfChild.Name.Equals(name))
return childOfChild;
}
}
}
return null;
}
public static IEnumerable<T> FindVisualChildren<T>(DependencyObject depObj) where T : DependencyObject
{
if (depObj == null)
yield break;
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
{
if (IsVisual(depObj))
{
DependencyObject child = VisualTreeHelper.GetChild(depObj, i);
if (child != null && child is T)
{
yield return (T) child;
}
foreach (T childOfChild in FindVisualChildren<T>(child))
{
yield return childOfChild;
}
}
}
}
private static bool IsVisual(DependencyObject depObj)
{
return depObj is Visual || depObj is Visual3D;
}
}
In this case use :
public override void OnApplyTemplate()
{
Border border = VisualTreeHelperExtensions.FindVisualChild<Border>(this,"Background") as Border;
border.CornerRadius = this.CornerRadius;
}
I have a tree which represents XML document. And I show XML validations errors using a bound class which implements INotifyDataErrorInfo.
It works all fine on editable controls like text box or combo.
But some nodes are just text blocks. And they have errors too.
How do I go about it?
I recommend you wrap your TextBlock in a ValidationErrorBorder:
<ValidationErrorBorder DataContextValidationTargetPath="MyTextProperty">
<TextBlock Text="{Binding Path=MyTextProperty}"/>
</ValidationErrorBorder>
ValidationErrorBorder control code:
/*
* Use in conjuction with other Controls that do not have the appropriate
* visualStates to indicate ValidationErrors.
* Either set DataContextValidationTargetPath to have this
* ValidationErrorBorder show validation errors that occur at the targeted
* property of the DataContext
* or bind ValidationTarget to any target you wish to have this
* ValidationErrorBorder show validation errors occuring for the bound target.
*/
[TemplateVisualState(GroupName="ValidationStates", Name = "Valid")]
[TemplateVisualState(GroupName="ValidationStates", Name = "InvalidUnfocused")]
[TemplateVisualState(GroupName="ValidationStates", Name = "InvalidFocused")]
public class ValidationErrorBorder : ContentControl
{
public object ValidationTarget
{
get { return GetValue( ValidationTargetProperty ); }
set { SetValue( ValidationTargetProperty, value ); }
}
public static readonly DependencyProperty ValidationTargetProperty =
DependencyProperty.Register( "ValidationTarget", typeof( object ),
typeof( ValidationErrorBorder ), new PropertyMetadata( null ) );
public string DataContextValidationTargetPath
{
get
{
return (string) GetValue( DataContextValidationTargetPathProperty );
}
set { SetValue( DataContextValidationTargetPathProperty, value ); }
}
public static readonly DependencyProperty
DataContextValidationTargetPathProperty =
DependencyProperty.Register( "DataContextValidationTargetPath",
typeof( string ), typeof( ValidationErrorBorder ),
new PropertyMetadata( null, HandlePathChanged ) );
private static void HandlePathChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
((ValidationErrorBorder) d).HandlePathChanged();
}
private void HandlePathChanged()
{
if (DataContextValidationTargetPath != null)
SetBinding(ValidationTargetProperty,
new Binding(DataContextValidationTargetPath));
else
ClearValue( ValidationTargetProperty );
}
public ValidationErrorBorder()
{
DefaultStyleKey = typeof( ValidationErrorBorder );
}
}
and the control template:
<Style TargetType="controls:ValidationErrorBorder">
<Setter Property="IsTabStop" Value="False"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="controls:ValidationErrorBorder">
<Grid x:Name="RootElement" Background="{x:Null}">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="ValidationStates">
<VisualState x:Name="Valid"/>
<VisualState x:Name="InvalidUnfocused">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility" Storyboard.TargetName="ValidationErrorElement">
<DiscreteObjectKeyFrame KeyTime="0" Value="Visible"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="InvalidFocused">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility" Storyboard.TargetName="ValidationErrorElement">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="IsOpen" Storyboard.TargetName="validationTooltip">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<system:Boolean>True</system:Boolean>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid>
<ContentPresenter Content="{TemplateBinding Content}" ContentTemplate="{TemplateBinding ContentTemplate}" />
<Border x:Name="ValidationErrorElement" BorderBrush="#FFDB000C" BorderThickness="1" CornerRadius="1" Visibility="Collapsed">
<ToolTipService.ToolTip>
<ToolTip x:Name="validationTooltip" DataContext="{Binding RelativeSource={RelativeSource TemplatedParent}}" Placement="Right" PlacementTarget="{Binding RelativeSource={RelativeSource TemplatedParent}}" Template="{StaticResource ValidationToolTipTemplate}">
<ToolTip.Triggers>
<EventTrigger RoutedEvent="Canvas.Loaded">
<BeginStoryboard>
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="IsHitTestVisible" Storyboard.TargetName="validationTooltip">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<system:Boolean>True</system:Boolean>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</ToolTip.Triggers>
</ToolTip>
</ToolTipService.ToolTip>
<Grid Background="Transparent" HorizontalAlignment="Right" Height="12" Margin="1,-4,-4,0" VerticalAlignment="Top" Width="12">
<Path Data="M 1,0 L6,0 A 2,2 90 0 1 8,2 L8,7 z" Fill="#FFDC000C" Margin="1,3,0,0"/>
<Path Data="M 0,0 L2,0 L 8,6 L8,8" Fill="#ffffff" Margin="1,3,0,0"/>
</Grid>
</Border>
</Grid>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
and the validationTooltipTemplate:
<ControlTemplate x:Key="ValidationToolTipTemplate">
<Grid x:Name="Root" Margin="5,0" Opacity="0" RenderTransformOrigin="0,0">
<Grid.RenderTransform>
<TranslateTransform x:Name="xform" X="-25"/>
</Grid.RenderTransform>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="OpenStates">
<VisualStateGroup.Transitions>
<VisualTransition GeneratedDuration="0"/>
<VisualTransition GeneratedDuration="0:0:0.2" To="Open">
<Storyboard>
<DoubleAnimation Duration="0:0:0.2" To="0" Storyboard.TargetProperty="X" Storyboard.TargetName="xform">
<DoubleAnimation.EasingFunction>
<BackEase Amplitude=".3" EasingMode="EaseOut"/>
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
<DoubleAnimation Duration="0:0:0.2" To="1" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="Root"/>
</Storyboard>
</VisualTransition>
</VisualStateGroup.Transitions>
<VisualState x:Name="Closed">
<Storyboard>
<DoubleAnimation Duration="0" To="0" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="Root"/>
</Storyboard>
</VisualState>
<VisualState x:Name="Open">
<Storyboard>
<DoubleAnimation Duration="0" To="0" Storyboard.TargetProperty="X" Storyboard.TargetName="xform"/>
<DoubleAnimation Duration="0" To="1" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="Root"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Border Background="#052A2E31" CornerRadius="5" Margin="4,4,-4,-4"/>
<Border Background="#152A2E31" CornerRadius="4" Margin="3,3,-3,-3"/>
<Border Background="#252A2E31" CornerRadius="3" Margin="2,2,-2,-2"/>
<Border Background="#352A2E31" CornerRadius="2" Margin="1,1,-1,-1"/>
<Border Background="#FFDC000C" CornerRadius="2"/>
<Border CornerRadius="2">
<TextBlock Foreground="White" MaxWidth="250" Margin="8,4,8,4" TextWrapping="Wrap" Text="{Binding (Validation.Errors)[0].ErrorContent}" UseLayoutRounding="false"/>
</Border>
</Grid>
</ControlTemplate>
It is not really supported in Silverlight by default. One way that I have found to work well in the past is to make a control that inherits from Textblock. You can then handle the IDatanotify interface and exceptions and turn the textblock different colors based off of those.
The AutoCompleteBox is pretty great, but one feature it lacks is a click-to-drop-down all the available options. You can come close by setting MinimumPrefixLength=0 -- that way, the user can get the complete drop-down list by deleting the text. This has a couple of limitations, though:
if there is no text to begin with, then the user would have to enter some text and delete it (non-intuitive and inconvenient)
the click-and-keypress sequence seems like suboptimal UX.
How would you tweak this control to make it drop down the complete list of options, when the user clicks on the control (or, also fine, a button to the right)?
It seems I have disappointed #HighCore. So far I tried adding a button to the control template, which triggers opening the Popup. The drawback to this approach is that, if there is text entered, then the list will be filtered per the normal filtering rules. Now, you could clear the text, thus removing the filter, but this has another side effect: de-selecting the currently selected item (in contrast to a ComboBox, whose drop-down you can open without de-selecting). So ... what now? Temporarily remove the filter, and restore it when the popup is closed or the user types anything else?
I built a control like that, the approach I used was to inherit from the AutoComboBox control and remove the filter when the dropdown opens.
EDIT
Code has been updated to include missing resources.
Code is:
public class AutoCompleteComboBox : AutoCompleteBox
{
/// <summary>
/// Occurs when drop down toggle button is clicked.
/// </summary>
public event EventHandler ToggleButtonClick;
private object _holdSelectedItem;
private AutoCompleteFilterMode _holdFilterMode;
/// <summary>
/// Identifies the DisplayMemberPath dependency property.
/// </summary>
public static readonly DependencyProperty DisplayMemberPathProperty = DependencyProperty.Register("DisplayMemberPath", typeof(string), typeof(AutoCompleteComboBox), new PropertyMetadata(string.Empty, DisplayMemberPath_PropertyChanged));
/// <summary>
/// Gets or sets the name or path of the property
/// that is displayed for each the data item in the control.
/// </summary>
public string DisplayMemberPath
{
get { return (string)GetValue(DisplayMemberPathProperty); }
set { SetValue(DisplayMemberPathProperty, value); }
}
private static void DisplayMemberPath_PropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var accb = (AutoCompleteComboBox)d;
accb.ValueMemberPath = e.NewValue.ToString();
}
/// <summary>
/// Identifies the MaxLength dependency property.
/// </summary>
public static readonly DependencyProperty MaxLengthProperty = DependencyProperty.Register("MaxLength", typeof(int), typeof(AutoCompleteComboBox), null);
/// <summary>
/// Gets or sets the maximum number of characters allowed for user input.
/// </summary>
public int MaxLength
{
get { return (int)GetValue(MaxLengthProperty); }
set { SetValue(MaxLengthProperty, value); }
}
/// <summary>
/// Gets or sets a collection used to generate the content of the control.
/// </summary>
public new IEnumerable ItemsSource
{
get { return GetValue(ItemsSourceProperty) as IEnumerable; }
set
{
SetValue(SelectedItemProperty, null);
SetValue(ItemsSourceProperty, value);
Dispatcher.BeginInvoke(() => SetValue(TextProperty, string.Empty));
_holdSelectedItem = null;
}
}
/// <summary>
/// Initializes a new instance of the AutoCompleteComboBox control.
/// </summary>
public AutoCompleteComboBox()
{
StreamResourceInfo sri = Application.GetResourceStream(new Uri("/UI.Controls;component/AutoCompleteComboBox.xaml", UriKind.Relative));
var sr = new StreamReader(sri.Stream);
Style = (Style)XamlReader.Load(sr.ReadToEnd());
DropDownClosed += AutoCompleteComboBox_DropDownClosed;
DropDownOpened += AutoCompleteComboBox_DropDownOpened;
}
/// <summary>
/// Called when the template's tree is generated.
/// </summary>
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
var toggle = (ToggleButton)GetTemplateChild("PopupToggleButton");
toggle.Click += DropDownToggle_Click;
var lb = (ListBox)GetTemplateChild("Selector");
lb.DisplayMemberPath = DisplayMemberPath;
_holdFilterMode = FilterMode;
TextChanged += AutoCompleteComboBox_TextChanged;
SelectionChanged += new SelectionChangedEventHandler(AutoCompleteComboBox_SelectionChanged);
}
private void AutoCompleteComboBox_DropDownClosed(object sender, RoutedPropertyChangedEventArgs<bool> e)
{
if (SelectedItem == null && _holdSelectedItem != null && SelectedItem != _holdSelectedItem && ItemsSource.Cast<object>().Contains(_holdSelectedItem))
{
SelectedItem = _holdSelectedItem;
}
_holdSelectedItem = null;
FilterMode = _holdFilterMode;
}
private void AutoCompleteComboBox_DropDownOpened(object sender, RoutedPropertyChangedEventArgs<bool> e)
{
var lb = (ListBox)GetTemplateChild("Selector");
ScrollViewer sv = lb.GetScrollHost();
if (sv != null)
{
sv.ScrollToTop();
}
if (SelectedItem != null)
{
lb.SelectedItem = SelectedItem;
_holdSelectedItem = SelectedItem;
}
}
private void DropDownToggle_Click(object sender, RoutedEventArgs e)
{
IsDropDownOpen = !IsDropDownOpen;
if (ToggleButtonClick != null)
{
ToggleButtonClick(this, e);
}
if (IsDropDownOpen)
{
_holdFilterMode = FilterMode;
FilterMode = AutoCompleteFilterMode.None;
((TextBox)GetTemplateChild("Text")).SelectAll();
}
Focus();
}
private void AutoCompleteComboBox_TextChanged(object sender, RoutedEventArgs e)
{
if (IsDropDownOpen)
{
if (FilterMode == AutoCompleteFilterMode.None && FilterMode != _holdFilterMode)
{
FilterMode = _holdFilterMode;
}
ScrollViewer sv = ((ListBox)GetTemplateChild("Selector")).GetScrollHost();
if (sv != null)
{
sv.ScrollToTop();
}
}
}
private void AutoCompleteComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (!IsDropDownOpen && SelectedItem == null)
{
Text = string.Empty;
}
}
}
<Setter Property="Height" Value="24" />
<Setter Property="MinimumPopulateDelay" Value="1" />
<Setter Property="IsTextCompletionEnabled" Value="False" />
<Setter Property="MinimumPrefixLength" Value="0" />
<Setter Property="MaxDropDownHeight" Value="300" />
<Setter Property="IsTabStop" Value="False" />
<Setter Property="Padding" Value="2" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="BorderBrush" Value="#FF000000" />
<Setter Property="Background" Value="#FFFFFFFF" />
<Setter Property="Foreground" Value="#FF000000" />
<Setter Property="MinWidth" Value="45" />
<Setter Property="FilterMode" Value="Contains" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ctrls:AutoCompleteComboBox">
<Grid >
<Grid.Resources>
<Style x:Name="comboToggleStyle" TargetType="ToggleButton">
<Setter Property="Foreground" Value="#FF333333"/>
<Setter Property="Background" Value="#FF1F3B53"/>
<Setter Property="BorderBrush">
<Setter.Value>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#FFA3AEB9" Offset="0"/>
<GradientStop Color="#FF8399A9" Offset="0.375"/>
<GradientStop Color="#FF718597" Offset="0.375"/>
<GradientStop Color="#FF617584" Offset="1"/>
</LinearGradientBrush>
</Setter.Value>
</Setter>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Padding" Value="3"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ToggleButton">
<Grid>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal"/>
<VisualState x:Name="MouseOver">
<Storyboard>
<DoubleAnimation Duration="0" Storyboard.TargetName="BackgroundOverlay" Storyboard.TargetProperty="Opacity" To="1"/>
<ColorAnimation Duration="0" Storyboard.TargetName="BackgroundGradient" Storyboard.TargetProperty="(Shape.Fill).(GradientBrush.GradientStops)[3].(GradientStop.Color)" To="#7FFFFFFF"/>
<ColorAnimation Duration="0" Storyboard.TargetName="BackgroundGradient" Storyboard.TargetProperty="(Shape.Fill).(GradientBrush.GradientStops)[2].(GradientStop.Color)" To="#CCFFFFFF"/>
<ColorAnimation Duration="0" Storyboard.TargetName="BackgroundGradient" Storyboard.TargetProperty="(Shape.Fill).(GradientBrush.GradientStops)[1].(GradientStop.Color)" To="#F2FFFFFF"/>
</Storyboard>
</VisualState>
<VisualState x:Name="Pressed">
<Storyboard>
<DoubleAnimation Duration="0" Storyboard.TargetName="BackgroundOverlay2" Storyboard.TargetProperty="Opacity" To="1"/>
<DoubleAnimation Duration="0" Storyboard.TargetName="Highlight" Storyboard.TargetProperty="(UIElement.Opacity)" To="1"/>
<ColorAnimation Duration="0" Storyboard.TargetName="BackgroundGradient" Storyboard.TargetProperty="(Shape.Fill).(GradientBrush.GradientStops)[1].(GradientStop.Color)" To="#E5FFFFFF"/>
<ColorAnimation Duration="0" Storyboard.TargetName="BackgroundGradient" Storyboard.TargetProperty="(Shape.Fill).(GradientBrush.GradientStops)[2].(GradientStop.Color)" To="#BCFFFFFF"/>
<ColorAnimation Duration="0" Storyboard.TargetName="BackgroundGradient" Storyboard.TargetProperty="(Shape.Fill).(GradientBrush.GradientStops)[3].(GradientStop.Color)" To="#6BFFFFFF"/>
<ColorAnimation Duration="0" Storyboard.TargetName="BackgroundGradient" Storyboard.TargetProperty="(Shape.Fill).(GradientBrush.GradientStops)[0].(GradientStop.Color)" To="#F2FFFFFF"/>
</Storyboard>
</VisualState>
<VisualState x:Name="Disabled" />
</VisualStateGroup>
<VisualStateGroup x:Name="CheckStates">
<VisualState x:Name="Checked">
<Storyboard>
<DoubleAnimation Duration="0" Storyboard.TargetName="BackgroundOverlay3" Storyboard.TargetProperty="(UIElement.Opacity)" To="1"/>
<DoubleAnimation Duration="0" Storyboard.TargetName="Highlight" Storyboard.TargetProperty="(UIElement.Opacity)" To="1"/>
<DoubleAnimation Duration="0" Storyboard.TargetName="BackgroundGradient2" Storyboard.TargetProperty="(UIElement.Opacity)" To="1"/>
<ColorAnimation Duration="0" Storyboard.TargetName="BackgroundGradient2" Storyboard.TargetProperty="(Shape.Fill).(GradientBrush.GradientStops)[1].(GradientStop.Color)" To="#E5FFFFFF"/>
<ColorAnimation Duration="0" Storyboard.TargetName="BackgroundGradient2" Storyboard.TargetProperty="(Shape.Fill).(GradientBrush.GradientStops)[2].(GradientStop.Color)" To="#BCFFFFFF"/>
<ColorAnimation Duration="0" Storyboard.TargetName="BackgroundGradient2" Storyboard.TargetProperty="(Shape.Fill).(GradientBrush.GradientStops)[3].(GradientStop.Color)" To="#6BFFFFFF"/>
<ColorAnimation Duration="0" Storyboard.TargetName="BackgroundGradient2" Storyboard.TargetProperty="(Shape.Fill).(GradientBrush.GradientStops)[0].(GradientStop.Color)" To="#F2FFFFFF"/>
</Storyboard>
</VisualState>
<VisualState x:Name="Unchecked"/>
</VisualStateGroup>
<VisualStateGroup x:Name="FocusStates">
<VisualState x:Name="Focused">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="FocusVisualElement" Storyboard.TargetProperty="Visibility" Duration="0">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Unfocused" />
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Rectangle x:Name="Background" RadiusX="3" RadiusY="3" Fill="{TemplateBinding Background}" StrokeThickness="{TemplateBinding BorderThickness}" Stroke="{TemplateBinding BorderBrush}"/>
<Rectangle x:Name="BackgroundOverlay" Opacity="0" RadiusX="3" RadiusY="3" Fill="#FF448DCA" StrokeThickness="{TemplateBinding BorderThickness}" Stroke="#00000000"/>
<Rectangle x:Name="BackgroundOverlay2" Opacity="0" RadiusX="3" RadiusY="3" Fill="#FF448DCA" StrokeThickness="{TemplateBinding BorderThickness}" Stroke="#00000000"/>
<Rectangle x:Name="BackgroundGradient" RadiusX="2" RadiusY="2" StrokeThickness="1" Margin="{TemplateBinding BorderThickness}" Stroke="#FFFFFFFF">
<Rectangle.Fill>
<LinearGradientBrush StartPoint=".7,0" EndPoint=".7,1">
<GradientStop Color="#FFFFFFFF" Offset="0" />
<GradientStop Color="#F9FFFFFF" Offset="0.375" />
<GradientStop Color="#E5FFFFFF" Offset="0.625" />
<GradientStop Color="#C6FFFFFF" Offset="1" />
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
<Rectangle Opacity="0" x:Name="BackgroundOverlay3" RadiusX="3" RadiusY="3" Fill="#FF448DCA" StrokeThickness="{TemplateBinding BorderThickness}" Stroke="#00000000"/>
<Rectangle Opacity="0" x:Name="BackgroundGradient2" RadiusX="2" RadiusY="2" StrokeThickness="1" Margin="{TemplateBinding BorderThickness}" Stroke="#FFFFFFFF">
<Rectangle.Fill>
<LinearGradientBrush StartPoint=".7,0" EndPoint=".7,1">
<GradientStop Color="#FFFFFFFF" Offset="0" />
<GradientStop Color="#F9FFFFFF" Offset="0.375" />
<GradientStop Color="#E5FFFFFF" Offset="0.625" />
<GradientStop Color="#C6FFFFFF" Offset="1" />
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
<Rectangle x:Name="Highlight" RadiusX="2" RadiusY="2" Opacity="0" IsHitTestVisible="false" Stroke="#FF6DBDD1" StrokeThickness="1" Margin="{TemplateBinding BorderThickness}" />
<ContentPresenter
x:Name="contentPresenter"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
Margin="{TemplateBinding Padding}"/>
<Rectangle x:Name="FocusVisualElement" RadiusX="3.5" Margin="1" RadiusY="3.5" Stroke="#FF6DBDD1" StrokeThickness="1" Visibility="Collapsed" IsHitTestVisible="false" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<ControlTemplate x:Key="CommonValidationToolTipTemplate" TargetType="ToolTip">
<Grid x:Name="Root" Margin="5,0" RenderTransformOrigin="0,0" Opacity="0">
<Grid.RenderTransform>
<TranslateTransform x:Name="Translation" X="-25" />
</Grid.RenderTransform>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup Name="OpenStates">
<VisualStateGroup.Transitions>
<VisualTransition GeneratedDuration="0" />
<VisualTransition To="Open" GeneratedDuration="0:0:0.2">
<Storyboard>
<DoubleAnimation Storyboard.TargetName="Translation" Storyboard.TargetProperty="X" To="0" Duration="0:0:0.2">
<DoubleAnimation.EasingFunction>
<BackEase Amplitude=".3" EasingMode="EaseOut" />
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
<DoubleAnimation Storyboard.TargetName="Root" Storyboard.TargetProperty="Opacity" To="1" Duration="0:0:0.2" />
</Storyboard>
</VisualTransition>
</VisualStateGroup.Transitions>
<VisualState x:Name="Closed">
<Storyboard>
<DoubleAnimation Storyboard.TargetName="Root" Storyboard.TargetProperty="Opacity" To="0" Duration="0" />
</Storyboard>
</VisualState>
<VisualState x:Name="Open">
<Storyboard>
<DoubleAnimation Storyboard.TargetName="Translation" Storyboard.TargetProperty="X" To="0" Duration="0" />
<DoubleAnimation Storyboard.TargetName="Root" Storyboard.TargetProperty="Opacity" To="1" Duration="0" />
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Border Margin="4,4,-4,-4" Background="#052A2E31" CornerRadius="5" />
<Border Margin="3,3,-3,-3" Background="#152A2E31" CornerRadius="4" />
<Border Margin="2,2,-2,-2" Background="#252A2E31" CornerRadius="3" />
<Border Margin="1,1,-1,-1" Background="#352A2E31" CornerRadius="2" />
<Border Background="#FFDC000C" CornerRadius="2">
<TextBlock UseLayoutRounding="false" Foreground="White" Margin="8,4,8,4" MaxWidth="250" TextWrapping="Wrap" Text="{Binding (Validation.Errors)[0].ErrorContent}" />
</Border>
</Grid>
</ControlTemplate>
</Grid.Resources>
<TextBox x:Name="Text" VerticalContentAlignment="Center" Background="{TemplateBinding Background}" BorderThickness="{TemplateBinding BorderThickness}" BorderBrush="{TemplateBinding BorderBrush}" Foreground="{TemplateBinding Foreground}" MaxLength="{TemplateBinding MaxLength}" />
<ToggleButton x:Name="PopupToggleButton" Width="24" HorizontalAlignment="Right" Style="{StaticResource comboToggleStyle}" Margin="1" >
<Path x:Name="BtnArrow" Height="4" Width="8" Stretch="Uniform" Data="F1 M 301.14,-189.041L 311.57,-189.041L 306.355,-182.942L 301.14,-189.041 Z " HorizontalAlignment="Center">
<Path.Fill>
<SolidColorBrush x:Name="BtnArrowColor" Color="#FF333333"/>
</Path.Fill>
</Path>
</ToggleButton>
<Border x:Name="ValidationErrorElement" Visibility="Collapsed" BorderBrush="#FFDB000C" BorderThickness="1" CornerRadius="1">
<ToolTipService.ToolTip>
<ToolTip x:Name="validationTooltip" DataContext="{Binding RelativeSource={RelativeSource TemplatedParent}}" Template="{StaticResource CommonValidationToolTipTemplate}" Placement="Right" PlacementTarget="{Binding RelativeSource={RelativeSource TemplatedParent}}">
<ToolTip.Triggers>
<EventTrigger RoutedEvent="Canvas.Loaded">
<BeginStoryboard>
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="validationTooltip" Storyboard.TargetProperty="IsHitTestVisible">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<system:Boolean>true</system:Boolean>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</ToolTip.Triggers>
</ToolTip>
</ToolTipService.ToolTip>
<Grid Height="12" HorizontalAlignment="Right" Margin="1,-4,-4,0" VerticalAlignment="Top" Width="12" Background="Transparent">
<Path Fill="#FFDC000C" Margin="1,3,0,0" Data="M 1,0 L6,0 A 2,2 90 0 1 8,2 L8,7 z" />
<Path Fill="#ffffff" Margin="1,3,0,0" Data="M 0,0 L2,0 L 8,6 L8,8" />
</Grid>
</Border>
<Popup x:Name="Popup">
<ListBox x:Name="Selector" SelectionMode="Single" ScrollViewer.HorizontalScrollBarVisibility="Disabled" ScrollViewer.VerticalScrollBarVisibility="Auto" MaxHeight="{TemplateBinding MaxDropDownHeight}" />
</Popup>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="PopupStates">
<VisualStateGroup.Transitions>
<VisualTransition GeneratedDuration="0:0:0" To="PopupOpened" />
<VisualTransition GeneratedDuration="0:0:0" To="PopupClosed" />
</VisualStateGroup.Transitions>
<VisualState x:Name="PopupOpened">
</VisualState>
<VisualState x:Name="PopupClosed">
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="ValidationStates">
<VisualState x:Name="Valid" />
<VisualState x:Name="InvalidUnfocused">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ValidationErrorElement" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="InvalidFocused">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ValidationErrorElement" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="validationTooltip" Storyboard.TargetProperty="IsOpen">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<system:Boolean>True</system:Boolean>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
I worked out one approach. The basic idea is to overlay a button, either in a modified control template, or just flung on top of the view (I'll use the latter here for brevity's sake). The button thing does three things:
Clear the text (in order to restore the complete unfiltered list)
Set IsDropDownOpen = true to open the Popup
Restore the selected item to the ListBox template child
So basically, I have a setup like this:
<Grid>
<mycontrols:ExtendedAutoCompleteBox x:Name="autoCompleteBox" ... />
<Button HorizontalAlignment="Right" Click="Button_Click">
<Path Data="F1 M 301.14,-189.041L 311.57,-189.041L 306.355,-182.942L 301.14,-189.041 Z "
Fill="Black" Stretch="Uniform"
Width="8" Height="4"
/>
</Button>
</Grid>
The purpose of subclassing AutoCompleteBox is just to get access to the template child:
public class ExtendedAutoCompleteBox : AutoCompleteBox
{
private ListBox _listBox;
public override void OnApplyTemplate()
{
_listBox = GetTemplateChild("Selector") as ListBox;
}
public void ShowAllOptions()
{
var selectedItem = SelectedItem;
Text = "";
IsDropDownOpen = true;
_listBox.SelectedItem = selectedItem;
UpdateLayout();
_listBox.ScrollIntoView(selectedItem);
}
}
And so the button click will just call this "ShowAllOptions":
private void Button_Click(object sender, RoutedEventArgs e)
{
autoCompleteBox.ShowAllOptions();
}
It's not perfect -- for some reason the keyboard controls get screwy after clicking the button (if you press enter on an item, it does not select the item, but clears it instead) -- but it is better than nothing.
So just to illustrate what it does: let's say you have an item "x" selected, and you click the arrow button, then you will see the complete list:
I'm stuck trying to add a dependency property to a button. I have several buttons located in my header view and clicking on them changes the content in the ContentControl between different views. All this works great. I want the button that was clicked have a different forecolor than the others and it looks like I need to add a dependency property. I think I have all the pieces in place but can't figure out how to get them all to work together.
I have a string property named ViewState in my viewmodel which changes based upon the button being clicked. The property is changing and I'm calling RaisePropertyChanged when it happens. What do I need to do to bind the additional dependency property? I'm transitioning from the WinForm world and trying to mentally piece it all together but struggling a bit.
Here's what I have so far:
<Style TargetType="{x:Type Button}" x:Key="LocalButtonTemplate">
<Setter Property="Foreground" Value="White" />
<Setter Property="Background" Value="{x:Null}" />
<Setter Property="FontFamily" Value="Segoe UI" />
<Setter Property="FontSize" Value="18" />
<Setter Property="Cursor" Value="Hand" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border x:Name="outerBorder" Background="{TemplateBinding Background}" Margin="4">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="ViewState">
<VisualState x:Name="Dashboard">
<Storyboard>
<ColorAnimation Duration="0:0:0.1" To="Yellow"
Storyboard.TargetProperty="(TextElement.Foreground).(SolidColorBrush.Color)"
Storyboard.TargetName="contentPresenter"/>
</Storyboard>
</VisualState>
<VisualState x:Name="AccountTables">
<Storyboard>
<ColorAnimation Duration="0:0:0.1" To="Red"
Storyboard.TargetProperty="(TextElement.Foreground).(SolidColorBrush.Color)"
Storyboard.TargetName="contentPresenter"/>
</Storyboard>
</VisualState>
<VisualState x:Name="Normal">
<Storyboard>
<ColorAnimation Duration="0:0:0.1" To="Purple"
Storyboard.TargetProperty="(TextElement.Foreground).(SolidColorBrush.Color)"
Storyboard.TargetName="contentPresenter"/>
</Storyboard>
</VisualState>
<VisualState x:Name="MouseOver">
<Storyboard>
<ColorAnimation Duration="0:0:0.1" To="#35A84D"
Storyboard.TargetProperty="(TextElement.Foreground).(SolidColorBrush.Color)"
Storyboard.TargetName="contentPresenter"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid>
<Border x:Name="Background" BorderBrush="Transparent">
<Grid>
<ContentPresenter x:Name="contentPresenter" ContentTemplate="{TemplateBinding ContentTemplate}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
Margin="4,5,4,4"/>
</Grid>
</Border>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
My buttons:
<dxwuii:SplitPanel Margin="0,10,10,10" HorizontalAlignment="Right" Grid.Column="2" ItemSpacing="0" Orientation="Horizontal" ItemSizeMode="AutoSize" >
<Button Command="{Binding SendViewModelNameCommand, Mode=OneTime}" CommandParameter="AccountTablesViewModel" Style="{StaticResource LocalButtonTemplate}">Express Tables</Button>
<Button Command="{Binding SendViewModelNameCommand, Mode=OneTime}" CommandParameter="MappingViewModel" Style="{StaticResource LocalButtonTemplate}">Item Mapping</Button>
<Button Command="{Binding SendViewModelNameCommand, Mode=OneTime}" CommandParameter="ReportsViewModel" Style="{StaticResource LocalButtonTemplate}">Reports</Button>
<Button Command="{Binding SendViewModelNameCommand, Mode=OneTime}" CommandParameter="PostBalancesViewModel" Style="{StaticResource LocalButtonTemplate}">Post Balances</Button>
</dxwuii:SplitPanel>
Dependency Property Class:
namespace MyAppName.Model
{
public class StateManager : DependencyObject
{
public static string GetVisualStateProperty(DependencyObject obj)
{
return (string)obj.GetValue(VisualStatePropertyProperty);
}
public static void SetVisualStateProperty(DependencyObject obj, string value)
{
obj.SetValue(VisualStatePropertyProperty, value);
}
public static readonly DependencyProperty VisualStatePropertyProperty =
DependencyProperty.RegisterAttached(
"VisualStateProperty",
typeof(string),
typeof(StateManager),
new PropertyMetadata((dependencyObject, args) =>
{
var frameworkElement = dependencyObject as FrameworkElement;
if (frameworkElement == null)
return;
VisualStateManager.GoToState(frameworkElement, (string)args.NewValue, true);
}));
}
}
Set Tag and Attached property on your each button as below. Tag value will be the VisualState value to which button should go on click.
<Button Tag="AcountTables" local:StateManager.VisualStateProperty="{Binding YOURVIEWMODELPROPERTY}" Command="{Binding SendViewModelNameCommand, Mode=OneTime}" CommandParameter="AccountTablesViewModel" Style="{StaticResource LocalButtonTemplate}">Express Tables</Button>
Update your AttachedProperty like:
public static readonly DependencyProperty VisualStatePropertyProperty =
DependencyProperty.RegisterAttached(
"VisualStateProperty",
typeof(string),
typeof(StateManager),
new PropertyMetadata((dependencyObject, args) =>
{
var frameworkElement = dependencyObject as FrameworkElement;
if (frameworkElement == null)
return;
if (args.NewValue == frameworkElement.Tag.ToString())
{
VisualStateManager.GoToState(frameworkElement, (string)args.NewValue, true);
}
else
{
VisualStateManager.GoToState(frameworkElement, "Normal", true);
}
}));