WPF change aligment of label and Viewbox - wpf

So i have a label in a viewbox so when the application scales the label will also scale. However the user what to choose to AutoScale via Viewbox or No autoscaling and set the properties for the label itself.
So far i got this:
<Viewbox Name="vb">
<Label
Name="lblText"
Width="{Binding ElementName=Sign, Path=ActualWidth}"
Height="{Binding ElementName=Sign, Path=ActualHeight}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
HorizontalContentAlignment="{Binding TextAlignment}"
VerticalContentAlignment="Center"
Background="Transparent"
Content="{Binding ObjectName}"
FontFamily="{Binding Font_Family}"
FontSize="{Binding FontSize}"
Foreground="{Binding FontColor}" />
</Viewbox>
Now to get the label AutoSize i have to set the Width and Height to Auto. However when i do this i cannot longer use the HorizontalContentAlignment because the label is the size of the text. When bound to the viewbox size then i can use it but the AutoSize doesn't work.
I was thinking about a event then when the user changes the `` then a event will fire where i can set the properties of the viewbox and label in code.
So in short i'm looking for a way to create a textholding object that can AutoSize or set by hand.

You could simply set the Stretch property of the Viewbox by a DataTrigger, e.g. on a boolean Autoscale property:
<Viewbox>
<Viewbox.Style>
<Style TargetType="Viewbox">
<Setter Property="Stretch" Value="None"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Autoscale}" Value="True">
<Setter Property="Stretch" Value="Uniform"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Viewbox.Style>
...
</Viewbox>

Related

Create combobox with different items design using data trigger

I have defined the names and color in the backend using C# so I want to select the item cat and change it's other elements.
You don't show the XAML you are using to set up the ComboBox but best approach is to use a data template to set up the ComboBox items however you wish them to look. If you want the color box to not be shown if the Color brush is null, you could then add a trigger to the template to make it Hidden. Note that if you make it collapsed it will realign the layout of the other controls.
<DataTemplate x:Key="cbDataTemplate">
<StackPanel Orientation="Horizontal">
<Rectangle x:Name="clrBox" Fill="{Binding Color, TargetNullValue=Transparent}"
Stroke="Black" StrokeThickness="2" Width="16"/>
<TextBlock Text="{Binding Name}" Margin="10,0,0,0"/>
</StackPanel>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Color}" Value="{x:Null}">
<Setter TargetName="clrBox" Property="Visibility" Value="Hidden"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
then to use this DataTemplate simply add it to the ComboBox like this:
<ComboBox ItemTemplate="{StaticResource cbDataTemplate}" Width="150"/>
hope that's helpful...

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>

WPF ListBox virtualization screws up displayed items

Problem
We need to efficiently display a large (>1000) number of objects in a WPF ListBox control.
We are relying on the WPF ListBox’s virtualization (via VirtualizingStackPanel) to efficiently display these items.
Bug: The WPF ListBox control does not display items correctly when using virtualization.
How to Reproduce
We have distilled the problem to the standalone xaml shown below.
Copy and paste the xaml into XAMLPad.
Initially, there is no selected item in the ListBox, so as expected, all items are the same size and they completely fill the available space.
Now, click on the first item.
As expected, because of our DataTemplate, the selected item will expand to show additional information.
As expected, this causes the horizontal scrollbar to appear, since the selected item is now wider than the available space.
Now use the mouse to click and drag the horizontal scrollbar to the right.
Bug: the non-selected visible items no longer stretch to fill the available space. All the visible items should be the same width.
Is this a known bug?
Is there any way to fix this, either via XAML or programmatically?
<Page
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" >
<Page.Resources>
<DataTemplate x:Key="MyGroupItemTemplate">
<Border Background="White"
TextElement.Foreground="Black"
BorderThickness="1"
BorderBrush="Black"
CornerRadius="10,10,10,10"
Cursor="Hand"
Padding="5,5,5,5"
Margin="2"
>
<StackPanel>
<TextBlock Text="{Binding Path=Text, FallbackValue=[Content]}" />
<TextBlock x:Name="_details" Visibility="Collapsed" Margin="0,10,0,10" Text="[xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx]" />
</StackPanel>
</Border>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type ListBoxItem}},Path=IsSelected}"
Value="True">
<Setter Property="TextElement.FontWeight"
TargetName="_details"
Value="Bold"/>
<Setter Property="Visibility"
TargetName="_details"
Value="Visible"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</Page.Resources>
<DockPanel x:Name="LayoutRoot">
<Slider x:Name="_slider"
DockPanel.Dock="Bottom"
Value="{Binding FontSize, ElementName=_list, Mode=TwoWay}"
Maximum="100"
ToolTip="Font Size"
AutoToolTipPlacement="BottomRight"/>
<!--
I want the items in this ListBox to completly fill the available space.
Therefore, I set HorizontalContentAlignment="Stretch".
By default, the WPF ListBox control uses a VirtualizingStackPanel.
This makes it possible to view large numbers of items efficiently.
You can turn on/off this feature by setting the ScrollViewer.CanContentScroll to "True"/"False".
Bug: when virtualization is enabled (ScrollViewer.CanContentScroll="True"), the unselected
ListBox items will no longer stretch to fill the available horizontal space.
The only workaround is to disable virtualization (ScrollViewer.CanContentScroll="False").
-->
<ListBox x:Name="_list"
ScrollViewer.CanContentScroll="True"
Background="Gray"
Foreground="White"
IsSynchronizedWithCurrentItem="True"
TextElement.FontSize="28"
HorizontalContentAlignment="Stretch"
ItemTemplate="{DynamicResource MyGroupItemTemplate}">
<TextBlock Text="[1] This is item 1." />
<TextBlock Text="[2] This is item 2." />
<TextBlock Text="[3] This is item 3." />
<TextBlock Text="[4] This is item 4." />
<TextBlock Text="[5] This is item 5." />
<TextBlock Text="[6] This is item 6." />
<TextBlock Text="[7] This is item 7." />
<TextBlock Text="[8] This is item 8." />
<TextBlock Text="[9] This is item 9." />
<TextBlock Text="[10] This is item 10." />
</ListBox>
</DockPanel>
</Page>
I spent more time attempting this than I probably should have, and couldn't get it to work. I understand what's going on here, but in pure XAML, I'm having trouble figuring out how to solve the issue. I think I see how to solve the problem, but it involves a converter.
Warning: Things are going to get complicated as I explain my conclusions.
The underlying problem comes from the fact that the Width of the controls stretch to the Width of their container. When virtualization is enabled, the Width will not change. In the underlying ScrollViewer inside of ListBox, the ViewportWidth property corresponds to the Width you see. When another control stretches out further (you select it), the ViewportWidth is still the same, but the ExtentWidth shows the full width. Binding the width of all controls to that of the ExtentWidth should work...
But it doesn't. I set the FontSize to 100 for quicker testing in my case. When an item is selected, ExtentWidth="4109.13. Going down the tree to your ControlTemplate's Border, I see ActualWidth="4107.13". Why the 2 pixel difference? ListBoxItem contains a Border with 2 Pixel padding, causing the ContentPresenter to render slightly smaller.
I added the following Style with help from here to allow me to directly access the ExtentWidth:
<Style x:Key="{x:Type ListBox}" TargetType="ListBox">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBox">
<Border
Name="Border"
Background="White"
BorderBrush="Black"
BorderThickness="1"
CornerRadius="2">
<ScrollViewer
Name="scrollViewer"
Margin="0"
Focusable="false">
<StackPanel IsItemsHost="True" />
</ScrollViewer>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsEnabled" Value="false">
<Setter TargetName="Border" Property="Background"
Value="White" />
<Setter TargetName="Border" Property="BorderBrush"
Value="Black" />
</Trigger>
<Trigger Property="IsGrouping" Value="true">
<Setter Property="ScrollViewer.CanContentScroll" Value="false"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Note I added a name to ScrollViewer for this purpose.
Then, I attempted to bind the Width of your Border to the ExtentWidth:
Width="{Binding ElementName=scrollViewer, Path=ExtentWidth}"
However, because of that 2 pixel padding, the controls will resize in an infinite loop, with the padding adding 2 pixels to the ExtentWidth, which resizes the Border width, which adds 2 more pixels to the ExtentWidth, etc. until you delete the code and refresh.
If you added a Converter that subtracted 2 from the ExtentWidth, I think this might work. However, when the scroll bar does not exist (you have not selected anything), ExtentWidth="0". Thus, binding to MinWidth instead of Width may work better so the items appear correctly when no scroll bar is visible:
MinWidth="{Binding ElementName=scrollViewer, Path=ExtentWidth, Converter={StaticResource PaddingSubtractor}}"
A better solution would be if you could directly databind the MinWidth of the ListBoxItem itself. You could bind directly to ExtentWidth, and no converter would be necessary. However I have no idea how to get access to that item.
Edit: For organization sake, here's the clip required to do that. Makes everything else unnecessary:
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="MinWidth" Value="{Binding Path=ExtentWidth, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ScrollViewer}}}" />
</Style>
Thanks to Will's great analysis!
Based on Will's suggestion: "A better solution would be if you could directly databind the MinWidth of the ListBoxItem itself...However I have no idea how to get access to that item", I was able to implement that using pure xaml, as follows:
<ListBox x:Name="_list"
Background="Gray"
Foreground="White"
IsSynchronizedWithCurrentItem="True"
TextElement.FontSize="28"
HorizontalContentAlignment="Stretch"
ItemTemplate="{DynamicResource MyGroupItemTemplate}">
<!-- Here is Will's suggestion, implemented in pure xaml. Seems to work.
Next problem is if you drag the Slider to the right to increase the FontSize.
This will make the horizontal scroll bar appear, as expected.
Problem: the horizontal scroll bar never goes away if you drag the Slider to the left to reduce the FontSize.
-->
<ListBox.Resources>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="MinWidth" Value="{Binding Path=ExtentWidth, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ScrollViewer}}}" />
</Style>
</ListBox.Resources>
<TextBlock Text="[1] This is item 1." />
<TextBlock Text="[2] This is item 2." />
<TextBlock Text="[3] This is item 3." />
<TextBlock Text="[4] This is item 4." />
<TextBlock Text="[5] This is item 5." />
<TextBlock Text="[6] This is item 6." />
<TextBlock Text="[7] This is item 7." />
<TextBlock Text="[8] This is item 8." />
<TextBlock Text="[9] This is item 9." />
<TextBlock Text="[10] This is item 10." />
</ListBox>
I got the idea from Adam Nathan's great book, "Windows Presentation Foundation Unleashed".
So, this seems to fix the original problem.
New Problem
You notice that there is a Slider control in the xaml that let's you increase/decrease the ListBox font. The idea here was to allow the user the ability to scale the ListBox content up or down for easier visibility.
If you first drag the Slider to the right to increase the FontSize, this will make the horizontal scroll bar appear, as expected. The new problem is that the horizontal scroll bar never goes away if you drag the Slider to the left to reduce the FontSize.
Any ideas?

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