WPF RelativeSource not updating it's value - wpf

I'm trying to implement a button that uses XAML-defined icons to show a different state (like in the second comment here: https://codereview.stackexchange.com/questions/183871/wpf-button-with-xaml-defined-icon-that-changes-with-state). The icons are defined with a "Fill" property that uses a RelativeSource binding, like so:
<Canvas x:Key="icon_stop"
x:Shared="False"
Width="76"
Height="76"
Clip="F1 M 0,0L 76,0L 76,76L 0,76L 0,0">
<Rectangle
Width="28.5"
Height="28.5"
Canvas.Left="23.75"
Canvas.Top="23.75"
Stretch="Fill"
Fill="{Binding RelativeSource={
RelativeSource Mode=FindAncestor,
AncestorType=Control
}, Path=Foreground}"
/>
</Canvas>
The button is bound to a property in the window's ViewModel like this:
<Button Name="PlayStopButton"
Padding="20 0 20 0"
Command="{Binding Path=StartStopCommand}">
<ContentControl Style="{StaticResource style_icon}">
<Binding
Path="IsRunning"
UpdateSourceTrigger="PropertyChanged"
FallbackValue="{StaticResource icon_play}">
<Binding.Converter>
<converters:BoolToObjectConverter
TrueObject="{StaticResource icon_stop}"
FalseObject="{StaticResource icon_play}"
NullObject="{StaticResource icon_play}"
/>
</Binding.Converter>
</Binding>
</ContentControl>
</Button>
(I'm omitting the "style" part here and the "BoolToObjectConverter", that should be clear for the purpose)
The problem is that, when I run the program, the button gets correctly updated, but the icon is not visible. This is because the "icon_stop" resource is not updated with the correct "Fill" color. In fact, if I force it (for example to "Black") the icon shows correctly.
It seems to me that the "RelativeSource" bounded property gets not updated.
I think this is related to the fact that the resource is a "StaticResource" and thus gets created only once on the application loading.
I've found that there are also "DynamicResource" references in WPF, but I can't use them here (or at least I can't see how now as them must be bound to a dependency property and I have the value converter here).
How can I solve this?

Your XAML seems overly complicated. You may try something simple like this:
<Style TargetType="Button" x:Key="PlayButtonStyle">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Path Fill="{Binding Foreground,
RelativeSource={RelativeSource AncestorType=Button}}"
Data="{Binding}"/>
</DataTemplate>
</Setter.Value>
</Setter>
<Setter Property="Content" Value="M5,0 L25,15 5,30Z"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsRunning}" Value="True">
<Setter Property="Content">
<Setter.Value>
<RectangleGeometry Rect="0,0,30,30"/>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
Try it like this:
<Button Style="{StaticResource PlayButtonStyle}" Foreground="Red" />

Related

Change Background color to a Visual pattern based

I have a list of elements (simple buttons with plain textblock) which are color coded based on the list item content. User can update the Listitem and thus listitem color should change. For certain listitem background colors like "Red", I want to add a pattern as well.
I have added the following VisualPatterns in XAML:
<Window.Resources>
<VisualBrush x:Key="FwdPattern" TileMode="Tile" Viewport="0,0,15,15" ViewportUnits="Absolute" Viewbox="0,0,15,15" ViewboxUnits="Absolute">
<VisualBrush.Visual>
<Grid>
<Path Data="M 0 15 L 15 0" Stroke="Gray" />
</Grid>
</VisualBrush.Visual>
</VisualBrush>
<VisualBrush x:Key="BckPattern" TileMode="Tile" Viewport="0,0,15,15" ViewportUnits="Absolute" Viewbox="0,0,15,15" ViewboxUnits="Absolute">
<VisualBrush.Visual>
<Grid>
<Path Data="M 0 0 L 15 15" Stroke="Gray" />
</Grid>
</VisualBrush.Visual>
</VisualBrush>
</Window.Resources>
Button template used in ListItem is:
<Border Background="{Binding BackgroundClr}">
<Button Name="MyButton" Content="Testing">
<Button.Style>
<Style TargetType="{x:Type Button}">
<Setter Property="Background" Value="{Binding BackgroundClr}"/>
<Style.Triggers>
<!-- This does not work, see [http://stackoverflow.com/questions/39583263/brush-mvvm-binding-does-not-give-named-color/39583422#39583422][1] -->
<DataTrigger Binding="{Binding BackgroundClr}" Value="Red">
<Setter Property="Background" Value="{StaticResource BckPattern}"/>
</DataTrigger>
<!-- This does not work either, it goes in infinite loop
and StackOverflow exception is thrown-
probably because I am reading the background color in
the datatrigger and again updating it- but i dont know-->
<DataTrigger Binding="{Binding Background.Color, RelativeSource={RelativeSource Self}}" Value="Red">
<Setter Property="Background" Value="{StaticResource BckPattern}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
</Border>
Currently I have no other knowledge except the Button BackgroundClr from VM to determine if I need to provide a pattern or not.
Tried Solutions
One solution is to have a bound property- PatternName and based on it, determine which pattern to apply:
The above code works, but I have to have an additional property in VM
The other solution is to access VisualBrush in VM and directly apply the pattern in BackgroundClr - i have not figured out how to do this yet.
Which is a better solution or there any other way to achieve the same?
Thanks,
RDV
Change {Binding BackgroundClr} to {Binding BackgroundClr.Color}.

Bind header textblock isEnabled to parent Groupbox isEnabled

Referencing to this question: WPF Databinding: How do I access the "parent" data context?
I wanna do something similiar, but for the header of a Groupbox (because the header does not concern with the Box is being disabled and thus is always black while the rest is light gray. This looks a bit strange to me if all the content of the box is gray, the above is gray, but the box title itself stays black.
So I tried to use the approach mentioned in the linked question by flq to simply bind the isEnabled property of the header textblock to the isEnabled property of the groupbox but it seems that my binding at some point fails and I don't know where and why exactly.
heres my current code:
<GroupBox Header="Change Steps" Grid.Row="2" Grid.ColumnSpan="3" Name="gbChangeSteps">
<GroupBox.Style>
<Style TargetType="GroupBox">
<Setter Property="HeaderTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock Text="{Binding}" FontWeight="Bold" Height="19" Foreground="Black" IsEnabled="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type GroupBox}}, Path=isEnabled}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupBox.Style>
<!-- ... (some non relevant Content)-->
</GroupBox>
after additional research I found the post Disable groupBox including the groupBox name in WPF
that lead me, in combination with Properties->Create Databinding->Binding type->UIElement to the solution that fixed both problems, the one this question was about and the original one that lead to entire restyling, which was that letters like the small g got messed up in the header.
This is the code that fixed the issue:
<GroupBox.Style>
<Style TargetType="{x:Type GroupBox}">
<Setter Property="HeaderTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock Text="{Binding}" FontWeight="Bold" Height="19" IsEnabled="{Binding IsEnabled, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UIElement}}}">
<TextBlock.Style>
<Style>
<Style.Triggers>
<Trigger Property="Control.IsEnabled" Value="False">
<Setter Property="Control.Foreground" Value ="#FF6D6D6D" />
</Trigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupBox.Style>

Displaying an alternative text when no image has been assigned yet

I have an image control on which the user can drag and drop pictures. When no image has been dropped in there and the control is empty, I'd like to show an alternative text such as "Drop a picture here" to better indicate what is expected from him/her.
I can't figure out how to use triggers with this, this shows nothing at all and I can no longer drop pictures on my image control:
<StackPanel Grid.Column="1" Grid.Row="2" FlowDirection="LeftToRight" Orientation="Horizontal" Margin="0px 4px">
<StackPanel.Resources>
<DataTemplate x:Key="tmpTemplate">
<Border BorderThickness="2" BorderBrush="#FF969DFF" CornerRadius="2" VerticalAlignment="Top">
<DockPanel>
<Image Name="imgSelectedViewImage" Source="{Binding Image}" MinWidth="32" MinHeight="32" MaxWidth="48" MaxHeight="48"
HorizontalAlignment="Left" Stretch="None"
IsEnabled="{Binding EditMode}" Margin="2px"/>
<Label Content="Drag here" Name="AltText" Visibility="Collapsed"></Label>
</DockPanel>
</Border>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Path=Image}" Value="{x:Null}">
<Setter TargetName="AltText" Property="Visibility" Value="Visible" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</StackPanel.Resources>
I can't tell you what you've done to break your previously working drag and drop functionality and all I can do is to suggest you undo your changes to remedy that. However, if you want to display something when a collection is empty, then there's an easy way to do that. Using the collection's Count property is no good because it won't update when items are added or removed.
You can simply add an int property next to your item collection for this, but you have to make sure that you notify the INotifyPropertyChanged interface for it also when the collection property changes:
public ObservableCollection<YourItem> YourItems
{
get { return yourItems; }
set
{
yourItems = value;
NotifyPropertyChanged("YourItems");
NotifyPropertyChanged("YourItemsCount");
}
}
public int YourItemsCount
{
get { return YourItems.Count; }
}
Then you can use it in a simple DataTrigger like this:
<Grid>
<!-- Put your normal content here -->
<TextBlock FontSize="16" HorizontalAlignment="Center" VerticalAlignment="Center"
Text="Drop images here">
<TextBlock.Style>
<Style>
<Setter Property="TextBlock.Visibility" Value="Collapsed" />
<Style.Triggers>
<DataTrigger Binding="{Binding YourItemsCount}" Value="0">
<Setter Property="TextBlock.Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</Grid>
I did it by setting the TargetNullValue and the FallbackValue to a static resource. See the code snippet below. You can of course set it to everything else you want.
<Image Stretch="Uniform"
Grid.Row="0"
Grid.Column="1"
MaxHeight="250"
MaxWidth="250"
Margin="10"
Source="{Binding Path=Character.Portrait, TargetNullValue={StaticResource FallbackImage}, FallbackValue={StaticResource FallbackImage}}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDoubleClick">
<cal:ActionMessage MethodName="ChangePicture">
</cal:ActionMessage>
</i:EventTrigger>
</i:Interaction.Triggers>
</image>
Another approach, which could be more to your liking, as it uses triggers could be this solution: Handling null when binding to an Image in XAML (look at the accepted answer). However in my opinion setting TargetNullValue and the FallbackValue is the best way to go.

Trigger on an Inner/Attached property

Trigger on an Inner property
<Button BorderBrush="Black" BorderThickness="2" x:Name="TimeButton" ClickMode="Press" Click="SetTime_Click" Height="26" HorizontalAlignment="Left" Margin="15, 0, 0, 0" Style="{StaticResource ImageButtonStyle}" ToolTip="Set Time" Width="26">
<Button.Background>
<ImageBrush x:Name="TimeImageBrush" ImageSource="/YCS;component/Images/Clock.png" Stretch="Uniform" TileMode="None" />
</Button.Background>
</Button>
I need to make a trigger to set the ImageBrush in the Button.Background property to something different according to a boolean named HasHours which I can bind easily from my itemssource, any one knows how I can achieve this, I could not find any examples linking to this property....
I tried something like this
<Button.Triggers>
<DataTrigger Binding="{Binding HasHours}" Value="false">
<Setter TargetName="TimeImageBrush" Property="ImageSource" Value="/YCS;component/Images/ClockRed.png"/>
</DataTrigger>
</Button.Triggers>
but it gives me this error:
Cannot find the static member 'ImageSourceProperty' on the type 'ContentPresenter'.
Any help is much appreciated
This is perhaps not exactly an answer to your question.
First, i guess you won't be able to add a DataTrigger to the Triggers collection, since that only supports EventTriggers.
But, you could define the DataTrigger in the Button's Style. Here, instead of setting the ImageBrush's ImageSource property, simply set a new ImageBrush as Background.
<Button ...>
<Button.Style>
<Style TargetType="Button">
<Style.Triggers>
<DataTrigger Binding="{Binding HasHours}" Value="False">
<Setter Property="Background">
<Setter.Value>
<ImageBrush ImageSource="/YCS;component/Images/ClockRed.png"/>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
Put the image as Content, not as Background, since you have no content.
Put the DataTrigger in the Triggers of the Image, not of the Button.
You will have to seek for the DataContext of the Trigger :
So something like :
<Button ... >
<Image ... >
<Image.Triggers>
<DataTrigger
Binding="{Binding Path= HasHours, RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type Button}}}"
Value="false" >
<Setter Property="ImageSource" Value="/YCS;component/Images/ClockRed.png"/>
</DataTrigger>
</Image.Triggers>
</Image>
</Button>

WPF Bind to parent property from within nested element using style

I've been trying to build a text box with a hint that's displaying while it's empty.
I'm having trouble setting the hint text from within a style.
To be precise, this works (that is, it binds correctly):
<TextBox Tag="hint text">
<TextBox.Background>
<VisualBrush Stretch="None">
<VisualBrush.Visual>
<TextBlock Text="{Binding Tag, RelativeSource={RelativeSource AncestorType=TextBox}}" FontStyle="Italic" Foreground="LightGray" />
</VisualBrush.Visual>
</VisualBrush>
</TextBox.Background>
</TextBox>
but, when I move it to the Style, it doesn't:
<Style TargetType="TextBox" x:Key="stlHintbox">
<Style.Triggers>
<DataTrigger Binding="{Binding Text, RelativeSource={RelativeSource Mode=Self}}" Value="">
<Setter Property="Background">
<Setter.Value>
<VisualBrush Stretch="None">
<VisualBrush.Visual>
<TextBlock Tag="inner" Text="{Binding Tag, RelativeSource={RelativeSource AncestorType=TextBox}}"
FontStyle="Italic" Foreground="LightGray" />
</VisualBrush.Visual>
</VisualBrush>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
<TextBox Tag="hint text" Style="{StaticResource stlHintbox}" />
So what's the catch? How can I bind to ancestor property from within a style?
The problem is not with the RelativeSource but with the way you are using the VisualBrush. Recall that Styles are shared between the elements you apply them to. The reason that your example doesn't work is that, in effect you are trying to share a single textbox (the one you tagged "inner") with multiple parent textboxes.
To see why this is a problem, try a thought experiment: The inner textbox gets created once (roughly speaking, this will happen when the style is created). Which of the textboxes that the style gets applied to should be chosen as the ancestor of the inner text box when you use the RelativeSource binding?
This is why DataTemplates and ControlTemplates exist in WPF. Rather than actually instantiate visuals directly, they define a template that allow multiple copies of visuals to be created as needed.
Reativesource doesn't work as expected.
It is better to create watermark textbox using control template. But your version could work:
<Window.Resources>
<Style TargetType="TextBox" x:Key="stlHintbox">
<Style.Triggers>
<DataTrigger Binding="{Binding Text, RelativeSource={RelativeSource Mode=Self}}" Value="">
<Setter Property="TextBox.Background">
<Setter.Value>
<VisualBrush Stretch="None" Visual="{Binding ElementName=hintText}"/>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<StackPanel>
<TextBox Tag="hint text" x:Name="myTextBox" Style="{StaticResource stlHintbox}" />
<Border Visibility="Hidden">
<TextBlock x:Name="hintText" Text="{Binding Tag, ElementName=myTextBox}" FontStyle="Italic" Foreground="LightGray" />
</Border>
</StackPanel>

Resources