Getting a value from a higher databinding path - xaml

I have an object that contains a control defined in a control template, and an object which it gets notifications from.
These notification object keeps track of the status of the object, which translates on the screen to the colour in this instance, although they are used in other areas of the UI to reflect the state of these objects.
The rectangle colour changes fine on the status change, but I have another property which provides the foreground colour of the text.
However, because of the ContentPresenter getting the text from the control, I can no longer get the foreground colour with the TextBlock.Foreground setter in the style, as the binding is pointing elsewhere.
I could put the name of the object in the notification object as well which would get rid of this question and problem, but I have run up against a similar issue in other places, and as I am new to WPF would like to know the way to do it.
This is the main relevant part of the ControlTemplate 'AnimatedTemplate':
<Rectangle x:Name="ColourFill" Stroke="White" RadiusX="5" RadiusY="5">
<Rectangle.Fill>
<SolidColorBrush Color="{Binding Path=FillColour}"/>
</Rectangle.Fill>
<Rectangle.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Active}" Value="True" >
<DataTrigger.EnterActions>
<BeginStoryboard> ... </BeginStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
</Style.Triggers>
</Style>
</Rectangle.Style>
</Rectangle>
<ContentPresenter HorizontalAlignment="Center"
VerticalAlignment="Center"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Margin="2"
TextBlock.FontFamily="Segoe UI"
TextBlock.FontWeight="Bold">
<ContentPresenter.Style>
<Style>
<Setter Property="TextBlock.FontSize"
Value="{Binding Path=Content, RelativeSource={RelativeSource TemplatedParent},
Converter={StaticResource PushPinContentSizeConverter}}" />
<Setter Property="TextBlock.Foreground"
Value="{Binding Path=TextColour}"/>
</Style>
</ContentPresenter.Style>
</ContentPresenter>
this is created with:
ControlTemplate template = (ControlTemplate)_window.FindResource("AnimatedTemplate");
Control c = new ContentControl();
c.Template = template;
c.SetValue(ContentControl.ContentProperty, displayName);
c.DataContext = <the notification object referred to as the first binding>
_window.d1overlay.Children.Add(c);

Related

Custom control with text box and text block

I would like to build a validation text box, which would be a normal UWP TextBox wrapped within a StackPanel, which also contains a TextBlock. The intent is that a validation message can be shown beneath the text box when there is a validation error.
I know I can do this by creating a custom control, but this would require me to implement all the properties I need and create a bunch of dependency properties, etc.
I'm hoping there is an easier way, where I can just completely derive the text box, but override the display template for it and include a label beneath it.
You can get most of the way there in XAML using the built-in IDataErrorInfo-based validation machinery and defining a control template for the TextBox's Validation.ErrorTemplate. There's a pretty good article at this link:
The XAML from the article at the link above follows, also check out this discussion on WPF's built-in validation here.
<Style TargetType="{x:Type Label}">
<Setter Property="Margin" Value="5,0,5,0" />
<Setter Property="HorizontalAlignment" Value="Left" />
</Style>
<Style TargetType="{x:Type TextBox}">
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="Margin" Value="0,2,40,2" />
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<DockPanel LastChildFill="true">
<Border Background="OrangeRed" DockPanel.Dock="right" Margin="5,0,0,0"
Width="20" Height="20" CornerRadius="5"
ToolTip="{Binding ElementName=customAdorner,
Path=AdornedElement.(Validation.Errors)[0].ErrorContent}">
<TextBlock Text="!" VerticalAlignment="center" HorizontalAlignment="center"
FontWeight="Bold" Foreground="white" />
</Border>
<AdornedElementPlaceholder Name="customAdorner" VerticalAlignment="Center" >
<Border BorderBrush="red" BorderThickness="1" />
</AdornedElementPlaceholder>
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

UserControl style only visible in designer

I'd like to have page headers in my app with either an icon or text centered in a 50px high bar at the top of the page. Optionally with a back-button.
For this reason I use a UserControl on each page which gets either one of those styles applied: PageHeaderStyle or PageHeaderBackStyle.
My implementation of one of those is the following (style definition in my App.xaml):
<Style x:Key="PageHeaderBaseStyle" TargetType="UserControl">
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="VerticalAlignment" Value="Top" />
<Setter Property="Height" Value="50" />
<Setter Property="Width" Value="NaN" />
<Setter Property="Background" Value="{StaticResource CDColor}" />
</Style>
<Style x:Key="PageHeaderStyle" TargetType="UserControl" BasedOn="{StaticResource PageHeaderBaseStyle}">
<Setter Property="Content">
<Setter.Value>
<Grid Background="{StaticResource CDColor}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" DataContext="{StaticResource MainPageModel}">
<TextBlock Style="{StaticResource PageHeaderTextBlockStyle}" Text="{Binding Title}" Visibility="{Binding TitleVisibility}" />
<Image Style="{StaticResource PageHeaderIconStyle}" Source="{Binding Icon}" Visibility="{Binding IconVisibility}" />
</Grid>
</Setter.Value>
</Setter>
</Style>
Applied like it should be:
<UserControl Style="{StaticResource PageHeaderStyle}" />
Now first I had used "Template" and applied a DataTemplate with the grid component. But this didn't work. Then I changed it to directly set the Content of the UserControl. This does work: After building the designer shows the page header (before it showed only the blue selection border, but no content - it was transparent).
But as soon as I start debugging the app on the emulator it disappears and the running app only shows a blank spot where it should be.
Why is this so? I mean after all the designer already shows it, why does it disappear then, though?
FYI: I do not get any binding exceptions nor any other. It just doesn't show up.
PS: I tried setting the Background in the base style while setting the grid's background to transparent. This didn't work either - only a blank spot.
Solved the problem: Best approach is probably to use a ContentControl. Using the Content property did not work, though. You have to use the ContentTemplate property. Using that one does work just fine.

How to animate HubSection when visibility changed in WinRT Hub Control

I have a HubSection that is dynamically shown/hidden using Visibility Collapsed/Visible during runtime. I would like to animate the HubSection by having it expand and shrink instead of just instantly showing or hiding it.
Animations in WinRT tend to be a science, and I haven't found a good method yet.
If someone could help with a code snippet I would appreciate it. I would prefer to keep it to XAML if possible, rather than putting the animation in a code-behind. Thanks!
Assuming that the visibiity of the hubsection is controlled by a boolean property of a viewmodel, bound through a converter then the solution is simpler than I thought. Even if this is not the case I'm sure you can modify the solution below easily.
You will need to reference the Behaviors SDK for this to work. You can do that either by going to References>Add Reference>Windows 8.1>Extensions>Behaviors SDK or by opening your project in blend and dragging a DataTriggerBehavior on yor hubsection.
Necessary xmlns declarations
xmlns:Interactivity="using:Microsoft.Xaml.Interactivity"
xmlns:Core="using:Microsoft.Xaml.Interactions.Core"
xmlns:Media="using:Microsoft.Xaml.Interactions.Media"
<Page.Resources>
<Converters:BoolToVisConverter x:Key="BoolToVisConverter"/>
<Storyboard x:Name="Storyboard1">
<DoubleAnimationUsingKeyFrames EnableDependentAnimation="True" Storyboard.TargetProperty="(FrameworkElement.Width)" Storyboard.TargetName="hubSection">
<EasingDoubleKeyFrame KeyTime="0" Value="0"/>
<EasingDoubleKeyFrame KeyTime="0:0:1" Value="400"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</Page.Resources>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Hub Header="Hub">
<Interactivity:Interaction.Behaviors>
<Core:DataTriggerBehavior/>
</Interactivity:Interaction.Behaviors>
<HubSection Header="HubSection 0">
<DataTemplate>
<Grid/>
</DataTemplate>
</HubSection>
<HubSection x:Name="hubSection" Header="HubSection 1" Visibility="{Binding MyProperty, Converter={StaticResource BoolToVisConverter}}" Width="400" Background="#FFB02626">
<Interactivity:Interaction.Behaviors>
<Core:DataTriggerBehavior Binding="{Binding MyProperty}" Value="True">
<Media:ControlStoryboardAction Storyboard="{StaticResource Storyboard1}"/>
</Core:DataTriggerBehavior>
</Interactivity:Interaction.Behaviors>
<DataTemplate>
<Grid/>
</DataTemplate>
</HubSection>
</Hub>
<ToggleSwitch Header="ToggleSwitch" HorizontalAlignment="Left" Margin="824,249,0,0" VerticalAlignment="Top" IsOn="{Binding MyProperty, Mode=TwoWay}"/>
The xaml defines a hub with 2 hubsections a toggleswitch to manipulate the boolean property in the viewmodel and a storyboard to animate the second hubsection.
The trick is to use a DataTriggerBehavior instead of an EventTriggerBehavior.
You can even bind directly to the visibility property of the hubsection for the DataTriggerBevavior if you want.

XAML style is applied to only the first rectangle. How to make it apply to all?

In a Windows 8 (WinRT) app, I am creating my own XAML style to get a dotted rectangle. In the setter for the style, I use Property="StrokeDashArray" Value="1,4". I then create a bunch of rectangles, and then explicitly set the style of those rectangles to this style I created. The first rectangle shows up with a dotted border - but the other two don't. However, if in addition to the Style={StaticResource myDottedStyle} I also specify the StrokeDashArray with each rectangle, then all them correctly show up with dotted borders.
Why is the dotted border only showing up for the first rectangle? How can I create a Style that is applied to all the rectangles without specifying the StrokeDashArray for each of them?
Here is a full code sample. In Windows 8 RTM, create a Blank XAML app project, and replace the Grid in the MainPage.xaml with the following:
<Page.Resources>
<Style x:Key="myDottedStyle" TargetType="Rectangle">
<Setter Property="Stroke"
Value="{StaticResource ApplicationForegroundThemeBrush}"/>
<Setter Property="StrokeThickness" Value="2"/>
<Setter Property="StrokeDashArray" Value="1,4"/>
</Style>
</Page.Resources>
<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
<Rectangle Style="{StaticResource myDottedStyle}" Width="40"
HorizontalAlignment="Left"/>
<Rectangle Style="{StaticResource myDottedStyle}" Width="40"
HorizontalAlignment="Center"/>
<Rectangle Style="{StaticResource myDottedStyle}" Width="40"
HorizontalAlignment="Right"/>
</Grid>
Here is a screenshot of the output of this
I found a related question that talks about DataTemplates here but I can't figure out how to translate that into my problem.
You could optimize things a bit by not requiring it to re-draw the rectangle per each instance and substitute for a ContentControl instead since they appear the same but with minor differences. So something for example like;
<Style x:Key="MyDottedStyle" TargetType="ContentControl">
<!-- Add additional Setters Here -->
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ContentControl">
<Rectangle Stroke="{StaticResource ApplicationForegroundThemeBrush}"
StrokeThickness="2"
StrokeDashArray="1,4"
Width="40" Height="40"
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
Margin="{TemplateBinding Margin}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- And now actually place it on your view -->
<ContentControl Style="{StaticResource MyDottedStyle}" HorizontalAlignment="Center"/>
This will allow you to not only clean things up because you can take your Style template and slap it over into say a Resource Dictionary to reduce clutter, but also makes it a little more efficient since you're not re-drawing your shape every time it's required. Hope this helps! Cheers!

Metro XAML - Issues With TemplateBinding and SolidColorBrush

Here is a simple custom control to illustrate my issue
public sealed class TestControl : Control
{
public static DependencyProperty TestColorProperty = DependencyProperty.Register("TestColor", typeof(Brush), typeof(TestControl), new PropertyMetadata(new SolidColorBrush(Colors.Blue)));
public Brush TestColor
{
get { return (Brush)GetValue(TestColorProperty); }
set { SetValue(TestColorProperty, value); }
}
public TestControl()
{
this.DefaultStyleKey = typeof(TestControl);
}
}
As you can see, it has a single Brush dependency property, with a default value of Blue (set in the PropertyMetaData as shown above.
Here is the XAML for my control in Generic.xaml
<Style TargetType="local:TestControl">
<Setter Property="TestColor" Value="Red" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:TestControl">
<Border
Background="{TemplateBinding TestColor}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<TextBlock Text="TEST" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
As you can see, I set the TestColor Brush dependency property to Red in a Style setter - overriding the default value of Blue as declared in my PropertyMetaData.
Notice that my Border in my Template uses TemplateBinding to set the background to the brush as discussed.
So what color do you think the border background gets set ? Red or Blue ?
The answer is neither.
If I set a breakpoint in my control somewhere where this value should be available (e.g. OnApplyTemplate as an example) then the value is null, rather than Red (default) as expected. In fact I have set breakpoints at all of the lifecycle points in the control and the default value in ProprtyMetaData is never used.
Setting the value within the style does nothing either (it doesn't get set to Blue as per my style setter delaration. This suggests that the style setter is failing for SolidColorBrush somehow.
However, this works
public BlankPage()
{
this.InitializeComponent();
testcont.TestColor = new SolidColorBrush(Colors.Orange);
}
and this works as well:
<Grid Background="{StaticResource ApplicationPageBackgroundBrush}">
<local:TestControl TestColor="Green" />
</Grid>
but TemplateBinding just doesn't work, and this is important as Im trying to write re-useable custom controls.
Is this a bug ?
Dean
this is an issue we're looking to address. In the mean time, set it up like this:
<SolidColorBrush x:Key="DefaultTestColorBrush">Red</SolidColorBrush>
and then in your template:
<Setter Property="TestColor" Value="{StaticResource DefaultTestColorBrush}" />
Then you should be able to clear this hurdle for now.
Personally, I think it's a bug somewhere in xaml parser. Try something like this, it should work:
<Setter Property="SelectedDayBrush">
<Setter.Value>
<SolidColorBrush>#7F7F7F7F</SolidColorBrush>
</Setter.Value>
</Setter>
<Setter Property="SelectedDayBrush">
<Setter.Value>
<SolidColorBrush Color="Orange"/>
</Setter.Value>
</Setter>
Seems it isn't yet fixed. If you develop something like custom button and PointerOver effect is just lighter background, a trick that works is to have invisible PointerOverVisual with white background on top of the content. Then PointerOver animation will make it slightly visible. That's what I use:
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal"/>
<VisualState x:Name="PointerOver">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Opacity" Storyboard.TargetName="pointerOverVisual">
<DiscreteObjectKeyFrame KeyTime="0" Value="0.15"/>
</ObjectAnimationUsingKeyFrames>
.......
<Border x:Name="Border" BorderThickness="0" Background="{TemplateBinding Background}" Margin="3">
content here
</Border>
<Rectangle x:Name="pointerOverVisual" Fill="White" Opacity="0" Margin="3"/>
<Rectangle x:Name="FocusVisualWhite" IsHitTestVisible="False" Opacity="0" StrokeDashOffset="1.5" StrokeEndLineCap="Square" Stroke="{StaticResource FocusVisualWhiteStrokeThemeBrush}" StrokeDashArray="1,1"/>
<Rectangle x:Name="FocusVisualBlack" IsHitTestVisible="False" Opacity="0" StrokeDashOffset="0.5" StrokeEndLineCap="Square" Stroke="{StaticResource FocusVisualBlackStrokeThemeBrush}" StrokeDashArray="1,1"/>
I have a working solution that uses a custom control, which in turn is placed multiple times into a user control (which is nice a reusable in my frames).
(in generic.xaml, recall I created a custom control)
<Style TargetType="local:myPad">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:myPad">
<Path Data="..." Height="60" Width="30" Fill="{TemplateBinding myPadColor}"/>
(in myPad.cs)
public sealed class myPad : Control
{
public myPad()
{
this.DefaultStyleKey = typeof(myPad);
}
public SolidColorBrush myPadColor
{
get { return (SolidColorBrush)GetValue(PadColorProperty); }
set { SetValue(PadColorProperty, value); }
}
public static readonly DependencyProperty PadColorProperty =
DependencyProperty.Register("myPadColor", typeof(SolidColorBrush), typeof(myPad), new PropertyMetadata(null));
}
in my user control, I then drop the template control and can have multiple "color" versions of the same...
<UserControl
x:Class=....
<Canvas Width="235" Height="235">
<local:myPad x:Name="thisPad" myPadColor="White">
...
I cut out much of the excess stuff (...), but I think you get the idea here. I also believe now too, that you can use yet another binding for myPadColor in the user control or in code-behind to get creative.
btw, i'm using VS2012 with SP3.. so perhaps things have been fixed now.
also, i'm pretty new to programming XAML, but at Tim Heuer's blog I found the right way to do the dependency property (thanks Tim!)
if you replace the (null) value in the propertymetadata value with your (new SolidColorBrush(Colors.Black)) you will get a default value in design mode.
:)
hope that helps.
You can try the below code snippet to bind the TestColor,
Background="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TestColor}"
I hope it will be helpful.
Regards,
Rex