Weird behaviour when moving VisualBrush into a Style - wpf

I've been working on a solution to have a dashed Border control. After some browsing I came across this which works; https://stackoverflow.com/a/47300149/9703942
However, Ideally I want my borders to be controlled by styles so anyone can use them across our many projects. So I did the following:
<Style x:Key="Border-Dashed" TargetType="{x:Type Border}" BasedOn="{StaticResource Border-Base}">
<Setter Property="BorderThickness" Value="{StaticResource Border-Thickness-Solid}" />
<Setter Property="BorderBrush">
<Setter.Value>
<VisualBrush>
<VisualBrush.Visual>
<Rectangle Stroke="HotPink"
StrokeDashArray="4,4"
Width="{Binding Path=ActualWidth, UpdateSourceTrigger=PropertyChanged, RelativeSource={RelativeSource AncestorType={x:Type Border}}}"
Height="{Binding Path=ActualHeight, UpdateSourceTrigger=PropertyChanged, RelativeSource={RelativeSource AncestorType={x:Type Border}}}"/>
</VisualBrush.Visual>
</VisualBrush>
</Setter.Value>
</Setter>
</Style>
This just doesn't work. I believe the Rectangle is drawn before the width and height of the border are correctly set. However, once they are set it's not updating the binding correctly?
So my question is, what am I doing wrong and can I achieve what I want to in styles alone?
Any help would be greatly appreciated!

The problem here is that RelativeSource bindings only work on objects in the same visual or logical tree, and a brush assignment is not a child "control" as such.
In any case, using a VisualBrush when you really want a stroke seems to me a bit like trying to fit a square peg into a round hole. Personally I think Border should have supported templating so that the brush could have been replaced with a pen. That wasn't done, so if you want to do this properly then I think you're left with the choice of either re-writing the Border control to support this, or replacing it with ContentControl instead:
<Window.Resources>
<ControlTemplate x:Key="BorderControl" TargetType="ContentControl">
<Grid>
<Rectangle Stroke="HotPink" StrokeDashArray="4,4" />
<ContentPresenter Margin="1"/>
</Grid>
</ControlTemplate>
</Window.Resources>
<Grid Width="200" Height="100">
<ContentControl Template="{StaticResource BorderControl}">
<TextBlock Text="Whatever" />
</ContentControl>
</Grid>

Related

Excess border selection in WPF's Lisbox [duplicate]

I have a ListBox in which each item is a StackPanel. The StackPanel consist of an Image and a TextBlock below it:
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Margin="10">
<Image>
<Image.Source>
<BitmapImage UriSource="{Binding Path=ImageFilePath}"/>
</Image.Source>
</Image>
<TextBlock Text="Title" TextAlignment="Center"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
It looks like this:
When the user select an item, I get the default blue rectangle that surround the StackPanel:
Now, I want to make a different border for the selected-item, but I want it to surround only the image.
I know how to make a control template and put a custom border around the ContentPresenter, but this, of course, will surround the whole StackPanel, not only the Image.
I don’t know if making changes to the ContentPresenter is possible, and if it is a good idea at all. If there is other way to achieve the look I want, it will be fine as well.
Right, the ListBox's own ContentPresenter isn't helpful for what you're doing. You want to a) eliminate the ListBox's own selection visuals and b) replace them with something more suitable in the DataTemplate for your items.
The default selection visual is applied by the default template for ListBoxItem. So replace that template. Using a Style in the resources for your ListBox, apply your own control template to ListBoxItem. Not much to it, just present the content and don't provide a selection background. Then you handle the selection visuals with a trigger in your data template, where your image and your label are defined and you can apply changes to one and not the other. The below example works for me.
Note that there's some fiddling with the HorizontalAlignment on the Border element to make it cling to the Image element within it. Also, I wrote a quickie test viewmodel whose Items property is called Items; I assume this is not the name of the collection member you're using to populate your own ListBox.
<ListBox
Margin="8"
ItemsSource="{Binding Items}"
>
<ListBox.Resources>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<Grid>
<ContentPresenter />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.Resources>
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Border
x:Name="HighlightBorder"
BorderThickness="4"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Margin="10"
>
<Border.Style>
<Style TargetType="Border">
<!-- MUST set default BorderBrush via a style, if you set it at all.
As an attribute on the Border tag, it would override the effects of
the trigger below.
-->
<Setter Property="BorderBrush" Value="Transparent" />
</Style>
</Border.Style>
<Image Source="{Binding ImageFilePath}" />
</Border>
</Grid>
<DataTemplate.Triggers>
<DataTrigger
Binding="{Binding IsSelected, RelativeSource={RelativeSource AncestorType=ListBoxItem}}"
Value="True">
<Setter TargetName="HighlightBorder" Property="BorderBrush" Value="Orange" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

Prevent cropping of background watermark

I am using the following technique to implement a watermark (I'm doing this from code in an attached property, but the following XAML demonstrates the issue):
<Window x:Class="MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<DockPanel>
<TextBox DockPanel.Dock="Left" Name="txb">
<TextBox.Background>
<VisualBrush Stretch="None">
<VisualBrush.Visual>
<Border Background="White">
<TextBlock Text="Hint text" Foreground="DarkGray" RenderTransformOrigin="0.5,0.5" HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding IsKeyboardFocusWithin, ElementName=txb}" Value="True">
<Setter Property="Visibility" Value="Hidden" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</Border>
</VisualBrush.Visual>
</VisualBrush>
</TextBox.Background>
</TextBox>
<Label Content="Label text here" />
</DockPanel>
</Window>
However, within a DockPanel, the width of the first TextBox shrinks to a mininum, hiding the watermark.
How can I force WPF to take the width of the background into account when calculating the dimensions of the TextBox?
I imagine I can calculate the expected width of the text in the watermark and set the MinWidth of the control appropriately. However, I would prefer something simpler -- some property setting, or a databinding.
I don't know of any way to make the TextBox automatically consider its background brush when measuring its desired size. You could, however, explicitly bind its MinWidth to the width of the watermark:
<TextBox MinWidth="{Binding RelativeSource={RelativeSource Self},
Path=Background.Visual.ActualWidth}"
...>
To force the some extra padding around the watermark, set the Padding on the Border within VisualBrush.Visual.
Note that this is a rather fragile binding that could lead to runtime exceptions if the background ever gets changed. You might want to rework it.
Try this:
<Grid>
<TextBox Name="txb">
.....
<Label Content="Label text here" />
</Grid>

Binding the background colour of a control using a trigger in WPF/XAML

Okay, first off I have no experience of WPF whatsoever so please bear with me and apologies if my terminology is a little wayward... ;)
The following code snippet is part of a WPF application that I have inherited. The trigger governs whether mandatory fields on a particular form are highlighted or not. The code works but the highlighting seems to apply to the control and the border (??) which contains it.
<ItemsControl xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cal="clr-namespace:Caliburn.PresentationFramework.ApplicationModel;assembly=Caliburn.PresentationFramework"
x:Class="company.product.Jobs.JobParametersEditor"
IsTabStop="False">
<ItemsControl.ItemTemplate>
<DataTemplate>
<DockPanel MinHeight="30">
<TextBlock Text="{Binding DisplayName, Mode=OneWay}"
DockPanel.Dock="Left"
VerticalAlignment="Center"
MinWidth="120"
Margin="6,0" />
<Border>
<Border.Style>
<Style TargetType="{x:Type Border}">
<Setter Property="Background"
Value="{x:Null}" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsValid}"
Value="False">
<Setter Property="Background"
Value="Red" />
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<ContentControl cal:View.Model="{Binding ValueEditor}"
ToolTip="{Binding ToolTip}"
IsTabStop="False"
MinHeight="19"
VerticalAlignment="Center"
HorizontalAlignment="Stretch" />
</Border>
</DockPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
The result is a bit clunky so I would like to restrict the highlighting to the control only but I can't figure out how to do it. I've tried moving the trigger so that it applies to the ContentControl instead of the Border but that didn't work and fiddling about with border margins, padding and thickness hasn't had any effect either.
Could anybody enlighten me as to how to accomplish this?

How to draw shape or lines within a WPF button that size to the control without cause the button to expand forever?

I had a quick Google, and a quick look around StackOverflow, but couldn't find anyone else who'd run into this problem.
I want to create a close button with a little X in it. The close button will do things like fade in and out when you're hovering over the item containing it, change colour when you mouse over, and all the usual WPF loveliness. Bottom line, it doesn't seem like it should be that difficult, but I'm running into one of the weirdest issues ever before I've even got to this stage.
Here's my XAML style for the button:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style x:Key="TabCloseButtonStyle" TargetType="{x:Type Button}">
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Content">
<Setter.Value>
<Grid>
<Line Stroke="{Binding RelativeSource={RelativeSource AncestorType=Button}, Path=Foreground}"
X1="0"
Y1="0"
X2="{Binding RelativeSource={RelativeSource AncestorType=Grid}, Path=ActualWidth, Mode=OneWay}"
Y2="{Binding RelativeSource={RelativeSource AncestorType=Grid}, Path=ActualHeight, Mode=OneWay}"/>
<Line Stroke="{Binding RelativeSource={RelativeSource AncestorType=Button}, Path=Foreground}"
X1="0"
Y1="{Binding RelativeSource={RelativeSource AncestorType=Grid}, Path=ActualHeight, Mode=OneWay}"
X2="{Binding RelativeSource={RelativeSource AncestorType=Grid}, Path=ActualWidth, Mode=OneWay}"
Y2="0"/>
</Grid>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
And I create my button, just as a test, like this:
<Window x:Class="WpfTestApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="124" Width="569">
<Grid Background="#2b3c59">
<StackPanel Orientation="Horizontal">
<!-- Other controls removed for clarity -->
<Button Style="{DynamicResource TabCloseButtonStyle}"
HorizontalAlignment="Center"
VerticalAlignment="Bottom"
Padding="2"
Margin="0, 10, 0, 0"
MinWidth="50"
MinHeight="50"></Button>
</StackPanel>
</Grid>
</Window>
And here's where it all goes horribly wrong. When you run the application the button will expand infinitely, one pixel at a time, horizontally, and vertically until it hits the height of the window.
Now I can understand why this is happening: the Lines actually go one unit beyond the width and height of the Grid causing the Grid to expand by one unit, then there's a relayout, the data binding causes the lines to redraw, ad infinitum.
So, to try and deal with this I decided to put a in the grid but then, no matter what I do with HorizontalAlignment and VerticalAlignment, I end up with both the Canvas and the Grid having zero width and height, which means I don't get my cross, which is massively irritating. If I bind to the ActualWidth and ActualHeight properties of the button I just end up with an X that has the top-left corner centred on the centre of the button.
Does anybody have any idea at all what I can do to fix this? I'd be extremely grateful for any pointers - WPF is still a rather new beast to me.
Thanks!
You shouldn't have to resort to bindings just to do layout. As you've seen, the bindings and the layout systems don't work in concert (I think databinding gets priority over layout, but in your case, layout causes another databinding)
You should be able get quite a nice looking, stretchable X with a just a Path:
<Path Data="M0,0 L1,1 M0,1 L1,0" Stretch="Uniform" Stroke="Red" />
If you want it to scale with the button size instead of stretch (scale affects apparent stroke width), then just use a ViewBox
Instead of binding, I'd suggest using the Stretch and Margin or Padding properties, like the following
<Grid Margin="2">
<Line X1="0" Y1="0" Y2="1" X2="1" Stroke="Black" Stretch="Fill" />
<Line Y1="1" X2="1" Stroke="Black" Stretch="Fill" />
</Grid>
Update
So the problem with your example seems to be the min height & width. If you can't just use height & width, I'd suggest something like the following
<Grid MinHeight="{TemplateBinding MinHeight}" MinWidth="{TemplateBinding MinWidth}">
<Line X1="0" Y1="0" Y2="1" X2="1" Stroke="Black" Stretch="Fill" />
<Line Y1="1" X2="1" Stroke="Black" Stretch="Fill" />
</Grid>
Thanks everyone. Rob's solution with the Viewbox turned out to be the answer. The button behaves perfectly, even if I remove the MidWidth and MinHeight attributes from the Button declaration in MainWindow.xaml.
Here's my modified style:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style x:Key="TabCloseButtonStyle" TargetType="{x:Type Button}">
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Content">
<Setter.Value>
<Grid>
<Viewbox>
<Path Data="M0,0 L10,10 M0,10 L10,0"
Stretch="Uniform"
Stroke="{Binding RelativeSource={RelativeSource AncestorType=Button}, Path=Foreground, Mode=OneWay}" />
</Viewbox>
</Grid>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
Thanks again for all the suggestions!

WPF Image Zooming

I have a Viewbox with an Image inside of it. This is great since the Viewbox will scale the Image to fit the window. However, I need to be able to zoom the image to its full size and show scroll bars and I am having a hard time figuring out how to do this.
Here's what I have right now. Can anyone give some pointers on how I can modify this to implement the above functionality?
<Viewbox x:Name="viewbox">
<StackPanel>
<Image x:Name="image" Source="ranch.jpg" />
</StackPanel>
</Viewbox>
Edit:
Just to clarify. I need both ways of viewing the image, the viewbox style of fitting the window AND the ability to toggle to an Actual Size view that shows scrollbars and doesn't resize the image.
You don't need a Viewbox here, by putting the Image in a ScrollViewer and manipulating the VerticalScrollBarVisibility and HorizontalScrollBarVisibility properties, you can make the Image scale or not:
<Grid xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<CheckBox x:Name="chkActualSize" Grid.Row="0" Content="Actual Size"/>
<ScrollViewer Grid.Row="1">
<ScrollViewer.Style>
<Style TargetType="{x:Type ScrollViewer}">
<Setter Property="HorizontalScrollBarVisibility" Value="Disabled"/>
<Setter Property="VerticalScrollBarVisibility" Value="Disabled"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsChecked, ElementName=chkActualSize}" Value="True">
<Setter Property="HorizontalScrollBarVisibility" Value="Auto"/>
<Setter Property="VerticalScrollBarVisibility" Value="Auto"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ScrollViewer.Style>
<Image Source="http://sipi.usc.edu/database/misc/4.1.01.tiff" VerticalAlignment="Center" HorizontalAlignment="Center"/>
</ScrollViewer>
</Grid>
<ScrollViewer HorizontalScrollBarVisibility="Auto">
<Viewbox>
<Image Source="ranch.jpg"/>
</Viewbox>
</ScrollViewer>
Based on your edit that you need to toggle the two approaches, I would do this in one of two ways.
Have two elements with the image. The Image element inside a ScrollViewer without the Viewbox will give you the full size image, and the Viewbox version will scale it. Then you can toggle the two depending on what you want to show.
Use a binding expression on the Height and Width properties of the Image and enclose it inside the scrollviewer. When you want to scale it (in some sort of trigger), set the Height to a binding expression that accesses the ActualHeight property of the ScrollViewer or whatever container is just above that (using RelativeSource to access the nearest ancestor something like the following):
{Binding Path=ActualHeight,
RelativeSource={RelativeSource AncestorType={x:Type ScrollViewer}}}
Thought I would post my solution for anyone looking.
<Slider Width="200" Value="500" Interval="25" Maximum="1000" x:Name="TestImageSlider" Minimum="-50" />
<ScrollViewer Grid.Row="1" HorizontalScrollBarVisibility="Auto">
<Image Source="{Binding SelectedScannedImage.ScannedImage}" Width="{Binding Path=Value, ElementName=TestImageSlider}" />
</ScrollViewer>

Resources