ViewBox FontSize Issue - wpf

I am attempting to use a ViewBox to adjust the font size for different controls and not the control size itself. The problem is that whenever I put a control like a TextBox or ComboBox it'll resize the font properly, but the control will not stretch. I know this has to do with the 'infinite' amount of space the ViewBox can use.
I tried a different approach to where I had TextBlock inside of a ViewBox and bound another controls fontsize to that TextBlock.
<Grid Width=100>
<ViewBox>
<TextBlock Name=aTextBlock />
</ViewBox>
</Grid>
...
<Grid>
<TextBlock FontSize={Binding ElementName=aTextBlock, Path=FontSize}/>
</Grid>
This approach did not work. It would be nice if I could bind to the ActualFontSize but that does not exist.
One last approach was to use a ViewModelBase that bound the FontSize of aTextBlock to a variable and have the other textblock to that fontsize.
private double _currentFontSize;
public double GlobalFontSize
{
get { return _currentFontSize; }
set
{
_currentFontSize = value;
MessageBox.Show(_currentFontSize.ToString());
RaisePropertyChanged();
}
}
NOTE: I tried using int, double, and FontSize types for this class.
<Grid Width=100>
<ViewBox>
<TextBlock Name=aTextBlock
FontSize={Binding GlobalFontSize,
UpdateSourceTrigger=PropertyChanged,
Mode=TwoWay} />
</ViewBox>
</Grid>
...
<Grid>
<TextBlock FontSize={Binding GlobalFontSize}>
</Grid>
And this did not work either. I'm starting to feel like the ViewBox is more trouble than it's worth. If I am doing any of this incorrectly or if anyone has any recommendations I would appreciate it very much.

Related

Textbox will not stretch to fill viewbox

I want the font size of my labels and textboxes in my LOB form to grow and shrink with window resize or resolution change. To achieve this I've placed my labels and textboxes within viewboxes.
The labels and custom radio buttons behave as I expect, but the textboxes will not stretch horizontally to fill the viewbox (sorry can't post image because of rep). The textboxes will horizontally fill the viewbox if you type into them.
Here is an example of the code which I am working with:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.186*"/>
<ColumnDefinition Width="0.814*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="0.127*"/>
<RowDefinition Height="0.873*"/>
</Grid.RowDefinitions>
<Viewbox Margin="0,0,0.917,0">
<Label Content="First name:"/>
</Viewbox>
<Viewbox Grid.Column="1">
<TextBox TextWrapping="Wrap"/>
</Viewbox>
</Grid>
I've tried placing grid, stackpanel and dockpanel (with LastChildFill="True") within the viewbox and then placing the textboxes within these layout controls but this didn't work either. Is there anyway to get the textboxes to horizontally fill the parent viewbox?
This problem is similar to this: WPF TextBox won't fill in StackPanel but this problem is with stackpanels, not viewboxes.
I think what you want is not easily possible. ViewBox tells its children that they have inifite space, in the measure pass of the layouting. After that, the display is fit into the viewbox. Now a textbox, told to have infinite space, obviously can't stretch.
I have 2 solutions, which i think are not what you want, but might be helpful anyway.
The first:
<Viewbox Grid.Column="1" Grid.Row="0" Stretch="Uniform" >
<Grid Width="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Viewbox}}, Path=ActualWidth}">
<TextBox TextWrapping="Wrap" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/>
</Grid>
</Viewbox>
this will stretch you textbox infact, but disable the behaviour expected from the viewbox. Why? We told the ViewBox to keep the aspect ratio, and set the textbox to fill the whole width of the viewbox, which keeps the size of the textbox.
The second:
would be to add a height to the grid, taken from the label, modified with the scaletransform of its viewbox.
This one i haven't tried, but it would involve a value converter.
In conclusion: There is no easy way to achieve what you want, because of the way the viewbox layouts its children. If you just want to stretch horizontally, my first solution works fine, if you want to scale. My guess is you have to do it yourself.
If what you want doesn't work/isn't easy then fake it:
<TextBox GotFocus="TextBox_GotFocus" HorizontalAlignment="Stretch" BorderThickness="0" Grid.Column="1"/>
<Viewbox HorizontalAlignment="Left" Stretch="Uniform" Grid.Column="1">
<TextBox x:Name="NameTextBox" Width="50" TextWrapping="Wrap" Text="Text" BorderThickness="0"/>
</Viewbox>
private void TextBox_GotFocus(object sender, RoutedEventArgs e)
{
NameTextBox.Focus();
NameTextBox.SelectionStart = NameTextBox.Text.Length;
}
Basically what happens is another TextBox is behind the Viewbox and when the behind TextBox gets focus, it switches focus to the Viewbox's TextBox. This will produce some odd resizing since you have your grid setup with relative sizes. You will need to play around with your grid column/width sizes until you get the effect you desire.
Just use a converter
Set the FontSize like this:
FontSize="{Binding RelativeSource={RelativeSource Self}, Path=ActualHeight ,Converter={StaticResource heightconverter}, ConverterParameter=3}"
public class ProcentualHeightConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (parameter is string p)
{
if (value is double v)
{
var result = double.TryParse(p, out double param);
if (result)
{
return v / param;
}
}
}
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}

Binding OnwayToSource not working as expected - what alternatives?

Ive a business object - call it Fish (not derived from anything ie not a DependancyObject) that is displayed in a ListBox using a DataTemplate. Else where in code I need to know the rendered width of the TextBlock part of the Fish DataTemplate through a reference to a Fish. No problem I thought. I added a width and height properties to Fish class and in my data template I bound the TextBlock width/height to these using Mode=OnwayToSource.
Problem: the Width/Height are always NaN when setting my Fish.width/heigh properties. I tried this workaround:
OneWayToSource Binding seems broken in .NET 4.0
but it doesnt work either (value is always NaN).
I cant bind to ActualWidth/ActualHeight because they are read only (why can't I bind OnwayToSource on a readonly property!!)
What alternatives do I have? Do I have to derive Fish from DependancyObject and make my properties DPs?
XAML:
<DataTemplate DataType="{x:Type p:Fish}">
<Border BorderBrush="Black" BorderThickness="2" >
<TextBlock FontSize="14" TextAlignment="Center" VerticalAlignment="Center"
Width="{Binding Path=width, Mode=OneWayToSource}"
Height="{Binding Path=height, Mode=OneWayToSource}" ...
Code:
class Fish {
public double width { get; set; } // From DataTemplate TextBlock.Width.
public double height { get; set; } // From DataTemplate TextBlock.Height
}
...
double rendered_width = my_fish.width; // Use the rendered width!
I've finally realized what you're trying to do, and you're right that it should work. WPF, however, disagrees. I see that it's a problem that others have had before, but that is apparently by design. You can't set up a binding on a read only property, even if you're just wanting to bind OneWayToSource.
Here is a question with the same problem: OneWayToSource binding from readonly property in XAML Their workaround was to put a container (which has read/write width/height) around the xaml element and set up the binding on that container. This might work for you.
There is an unresolved issue related to this on Microsoft Connect where it is claimed to be behaviour by design: http://connect.microsoft.com/VisualStudio/feedback/details/540833/onewaytosource-binding-from-a-readonly-dependency-property. Someone claims a workaround in the related thread which uses a converter. You can try it, but I'm not sure it'll work in your case, as their binding was to a custom control, not a built in framework element.
Even Better
In This Solution, Boogaart came up with an implementation defining a new attached property (Similar to DockPanel.Dock="Top") which allows any element to provide its width and height for observation:
<TextBlock ...
SizeObserver.Observe="True"
SizeObserver.ObservedWidth="{Binding Width, Mode=OneWayToSource}"
SizeObserver.ObservedHeight="{Binding Height, Mode=OneWayToSource}"
Try it on and see if it fits.
If you consume these properties after some sort of action i.e. a button press or click on a hyperlink, then you can pass in the the ActualWidth and Height via a CommandParameter of a command. Otherwise I would suggest using triggers such as the ones available here:
http://expressionblend.codeplex.com/wikipage?title=Behaviors%20and%20Effects&referringTitle=Documentation
I agree that it appears counter intuitive that OneWayToSource bindings don't work on read only dependency properties.
Try binding OneWay. I think OneWayToSource is means wants to write to the source.
http://msdn.microsoft.com/en-us/library/system.windows.data.bindingmode.aspx
I did a test and sure enough Width = NaN until width is Assigned (set). I understand this is not the behavior you want. Try this. Where the Width is assigned it is reported (as 200). Where the Width is not assigned it is reported as NaN. But ActualWidth IS correct. ActualWidth is there but clearly the way you are trying to get it is not working.
<StackPanel Orientation="Vertical">
<Border BorderThickness="1" BorderBrush="Red">
<TextBlock Name="tbwidthA" Text="{Binding Path=Howdy}" HorizontalAlignment="Left" Width="200"/>
</Border>
<TextBlock Name="tbwidthAw" Text="{Binding ElementName=tbwidthA, Path=Width}" HorizontalAlignment="Left"/>
<TextBlock Name="tbwidthAaw" Text="{Binding ElementName=tbwidthA, Path=ActualWidth}" HorizontalAlignment="Left" />
<Border BorderThickness="1" BorderBrush="Red">
<TextBlock Name="tbwidthB" Text="{Binding Path=Howdy}" HorizontalAlignment="Left" />
</Border>
<TextBlock Name="tbwidthBw" Text="{Binding ElementName=tbwidthB, Path=Width}" HorizontalAlignment="Left" />
<TextBlock Name="tbwidthAbw" Text="{Binding ElementName=tbwidthB, Path=ActualWidth}" HorizontalAlignment="Left" />
<Button Content="TBwidth" Click="Button_Click_1" Width="60" HorizontalAlignment="Left" />
</StackPanel>
What is interesting is the Button does report the correct ActualWidth but Width is NaN.
Debug.WriteLine(tbwidthB.Width.ToString());
Debug.WriteLine(tbwidthB.ActualWidth.ToString());

WPF ScrollViewer resize problem

I've got a scrollviewer in a WPF window that is resizing according to its contents no matter what I do. This is kind of a problem since it will resize my window as well, and in my opinion defeats the purpose of a scrollviewer.
The window in question has its SizeToContent set to "WidthAndHeight" which I know can be set to "Manual" to resolve the scrollviewer issues, but then I have to go and fiddle with my window layout.
Is there any way to get the best of both worlds here? All I want is for the scrollviewer to stay the same (its current) size any time its content changes.
EDIT:
If you really need to see it.....
<Window SizeToContent="WidthAndHeight">
<Grid Width="200" Name="ThinkBeforeSpeaking">
<ScrollViewer VerticalScrollBars="Auto" HorizontalScrollBars="Auto">
<ContentControl Content={Binding AnythingYouCanImagine}"/>
</ScrollViewer>
</Grid>
</Window>
Doesn't need to be a content control inside of the viewer, just anything bigger than the window in either dimension.
I've just come across this problem myself, and I've found a suitable solution in wrapping the ScrollViewer in a Canvas, so that it doesn't take part in the measuring pass, and telling the ScrollViewer to use the ActualHeight and ActualWidth of the Canvas so that it takes up all the space the Canvas has been given:
<Window SizeToContent="WidthAndHeight">
<Grid Width="200" Name="ThinkBeforeSpeaking">
<Canvas>
<ScrollViewer VerticalScrollBars="Auto" HorizontalScrollBars="Auto"
Height="{Binding Parent.ActualHeight, Mode=OneWay, RelativeSource={RelativeSource Self}}"
Width="{Binding Parent.ActualWidth, Mode=OneWay, RelativeSource={RelativeSource Self}}">
<ContentControl Content={Binding AnythingYouCanImagine}"/>
</ScrollViewer>
</Canvas>
</Grid>
</Window>
As it stands, there's nothing telling the window to have any height at all, so either one of the existing controls (Window, Grid or Canvas) needs a defined Height/MinHeight, or other controls need to be added which will provide their own Height/MinHeight.
I'm assuming you want your Window to start sized large enough to fit it's content, but then if the content changes it will not resize.
If so, you'd need to reset the SizeToContent option when the Window is loaded, like so:
public MainWindow() {
InitializeComponent();
this.Loaded += (s, e) => { this.SizeToContent = SizeToContent.Manual; };
}
In your example you're using the WidthAndHeight behavior for your Window. The Window's Width will expand to the 200px you set on your Grid, but then you have no control to dictate which Height the Window needs to have.
Consequently, the Window's Height will resize to the ScrollViewer's content, unless you specify a fixed Height or MaxHeight for your ScrollViewer or Grid (Or any other container between the Window and the ScrollViewer).
<Window SizeToContent="WidthAndHeight">
<Grid Width="200" Height="150" Name="ThinkBeforeSpeaking">
<ScrollViewer VerticalScrollBars="Auto" HorizontalScrollBars="Auto">
<ContentControl Content={Binding AnythingYouCanImagine}"/>
</ScrollViewer>
</Grid>
</Window>
Think of it, which Height would the Window have otherwise? None? Infinite? There's nothing in your XAML that specifies it.

WPF : Is it possible to adapt a path size to the layout size, but still stretching it?

I'm trying to style a TabItem Header, using a path to define the shape of the header.
I'm stuck in a problem that I can't seem to resolve :
If I set the path Stretch property to "None", it won't scale if the text in my TabItem Header is long.
If I set the path Stretch property to "Fill", it will stretch so much that each TabItem Header will be the same width as the TabControl - which mean only one very wide TabItem Header per line...
Do you know a way to stretch the path to the layout (depending on the TabItemHeader Content), but not more?
I would be very pleased if somebody can help me on this... it's been an annoying while I'm looking for a solution.
Thank you :-)
It's hard when you don't add any sample code but say that your HeaderTemplate looks like below then you can bind the Width of the Path to the ActualWidth of the TextBlock.
<TabItem.HeaderTemplate>
<DataTemplate>
<Border x:Name="grid">
<Grid>
<Path Data="..."
Stretch="Fill"
Width="{Binding ElementName=grid, Path=ActualWidth}" />
<TextBlock Name="textBlock"
Margin="4"
FontSize="15"
Text="{Binding}"/>
</Grid>
</Border>
</DataTemplate>
</TabItem.HeaderTemplate>
But be aware of rendering performance! Binding to ActualWidth and ActualHeight will result in binding errors as long as the UI hasn't been rendered enterly. And binding errors are expensive...The best way to avoid this, is to set up the binding in code when SizeChanged is called. That's the first moment after measuring and sizing has been finished.

WPF: GroupBox dynamic height

I have a textbox and datagrid inside of a dockpanel that is in a WPF groupbox.
<GroupBox Margin="8,142.04,1.783,230.4" Height="Auto" Header="Desired Meeting Outcomes (decisions or actions)?" MaxWidth="635" MinWidth="550" FontWeight="Bold" FontSize="13.333" BorderBrush="#FFD5DFE5" MinHeight="106" VerticalContentAlignment="Stretch">
<DockPanel Margin="0">
<local:TextboxControl Margin="0" d:LayoutOverrides="Height, HorizontalMargin" Width="538.217" VerticalAlignment="Top" HorizontalAlignment="Left" DockPanel.Dock="Top"/>
<local: Height="Auto" HorizontalAlignment="Left" MinHeight="25" MinWidth="538" DockPanel.Dock="Top"/>
</DockPanel>
</GroupBox>
I am adding rows in the datagrid dynmaically from the textbox causing the datagrid to grow. However, my groupbox's height is not growing dynamically even though its height is set to Auto. How can I get my groupbox to grow and shrink based upon the size of the contents that it holds?
You have margins set on all 4 sides with a VerticalAlignment of Stretch. In a Grid this will basically give you a GroupBox that sizes with its parent but not its content. Remove the margin from the right and bottom and change the VerticalAlignment to Top.
The margins are the order of L, T, R, B. So zero out the last two. Height=Auto and VerticalContentAlignment=Stretch are the defaults so you can get rid of those too. Try to keep the XAML as clean as possible.
It's clear from the markup that you're using Blend or Visual Studio's designer. I would suggest using the designer for "preview" mode rather than editing. Although it's gotten much better I find the layout behavior of the designer in both products to be very frustrating. Getting familiar with creating XAML by hand pays dividends in the long run.
EXAMPLE
As per the comments, I'm adding an example of how you would have a DataGrid that causes its parent elements to automatically grow based on height. Notice that only the Window itself has a fixed size. For a Window, if you wanted to make it grow based on height you could set SizeToContent=Height. Notice how you only need to set VerticalAlignment=Top on the outermost element.
MainWindow.xaml
<Window x:Class="WpfApplication2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="640" Height="480">
<Grid x:Name="LayoutRoot" Background="Green" VerticalAlignment="Top">
<Border Margin="5" BorderBrush="Yellow" BorderThickness="4">
<GroupBox Header="Data Grid" Background="Orange">
<DataGrid x:Name="dg" AutoGenerateColumns="True" />
</GroupBox>
</Border>
</Grid>
</Window>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public MainWindow( )
{
InitializeComponent( );
var items = new ObservableCollection<DateTime>( );
dg.ItemsSource = items;
var timer = new DispatcherTimer( );
timer.Interval = TimeSpan.FromSeconds( 2 );
timer.Tick += ( s, e ) => items.Add( DateTime.Now );
timer.Start( );
}
}
What is the container of the GroupBox ? It could prevent it from growing.
For example, if the container is the Windows, does it have SizeToContent="Height" ?

Resources