How can I change the TextBox's width with the text Length? - wpf

I want to remake one kind of game from those typing games, where the words are coming from somewhere and you have to write down that word correctly.
Here is one: https://www.youtube.com/watch?v=FqNTKJRBPdc
My first problem is that I don't find nothing, how could I change dynamically the TextBox's width.
So if the whole word won't fit in the TextBox, I want to increase the TextBox's width. How could I do it?
Here is my View:
<UserControl x:Class="Prog_korny.View.GameView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Prog_korny"
xmlns:vm="clr-namespace:Prog_korny.ViewModel"
mc:Ignorable="d"
d:DesignHeight="720" d:DesignWidth="1280" Name="alUO">
<UserControl.Resources>
<vm:GameViewModel x:Key="GameViewModel"/>
</UserControl.Resources>
<Grid DataContext="{Binding Source={StaticResource GameViewModel}}">
<Grid.Background>
<ImageBrush ImageSource="/Prog korny;component/Pictures/background.png"/>
</Grid.Background>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*" />
<ColumnDefinition Width="5*" />
<ColumnDefinition Width="3*" />
<ColumnDefinition Width="4*" />
<ColumnDefinition Width="3*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="7*" />
<RowDefinition Height="1*" />
<RowDefinition Height="6*" />
<RowDefinition Height="1*" />
</Grid.RowDefinitions>
<Canvas Grid.ColumnSpan="5" Grid.RowSpan="4"></Canvas>
<TextBox x:Name="txtText" Grid.Column="2" Grid.Row="1" Grid.ColumnSpan="1" Background="Transparent" Foreground="White" BorderBrush="Blue" FontFamily="Bebas Neue" FontSize="35" TextAlignment="Center" VerticalAlignment="Center" Cursor="Hand">
<TextBox.Text>
<Binding Path="textContent" UpdateSourceTrigger="PropertyChanged"/>
</TextBox.Text>
</TextBox>
<Label x:Name="lblLevel" Grid.Column="0" Grid.Row="4" Grid.ColumnSpan="1" Foreground="White" BorderBrush="{x:Null}" FontFamily="Bebas Neue" FontSize="30" Cursor="Hand">
<Label.Content>
<Binding Path="LevelLabel" UpdateSourceTrigger="PropertyChanged"/>
</Label.Content>
</Label>
<Label x:Name="lblScore" Grid.Column="4" Grid.Row="4" Grid.ColumnSpan="1" Foreground="White" BorderBrush="{x:Null}" FontFamily="Bebas Neue" FontSize="30" Cursor="Hand">
<Label.Content>
<Binding Path="ScoreLabel" UpdateSourceTrigger="PropertyChanged"/>
</Label.Content>
</Label>
</Grid>
</UserControl>
ViewModel:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Prog_korny.ViewModel
{
class GameViewModel : ViewModelBase
{
private string _textContent;
public string TextContent
{
get { return _textContent; }
set { _textContent = value; }
}
private string _levelLabel = "Level: 0";
public string LevelLabel
{
get { return _levelLabel; }
set { _levelLabel = value; }
}
private string _scoreLabel = "Score: 0";
public string ScoreLabel
{
get { return _scoreLabel; }
set { _scoreLabel = value; }
}
}
}

you can define a property which you will bind with the width property in xaml page.
You can control the property from viewmodel based on your given condition.
You can control almost everything which is available to you in your window xaml page.Plus you can define conditional logic based on pairing of two properties.
like if your TextContent property reaches certain limit then set text width property to something.
With low level of control you can achieve controlling width via data triggers.
<Style>
<Setter Property="Width" Value="200"/>
<Style.Triggers>
<<Put your conditions which will set the value>>
</Style.Triggers>
</Style>

Related

How to Bind Textbox.ReadOnly to property in the parent (owner) view model

It seems a resolved question but after trying several answers posted here I could not resolve my problem. Here it is:
I want to set (by binding) the property IsReadonly of a TextBox to a property of the main view model which contains the type to which the TextBox is already bound to. Also de text box is in a DataTemplate bound to a type.
Trying this produces "Property 'IsReadOnly' is not found" which has sense because the data template is bound to a type that doesn't have it:
<TextBox Name="PromoEntryForm" AutomationProperties.Name="Promo Description" Grid.Row="0" Grid.Column="1" HorizontalAlignment="Left"
Text="{Binding Path=FriendlyName, UpdateSourceTrigger=PropertyChanged}"
Style="{StaticResource TextStyleTextBox}" Margin="8,5,0,5"
IsReadOnly="{Binding Path=IsReadOnly}" />
Trying this produces nothing, which also has sense because it is binding to the same TextBox.IsReadOnly, as I understand:
<TextBox Name="DiscountEntryForm" AutomationProperties.Name="PromoDiscount" Grid.Row="1" Grid.Column="1" HorizontalAlignment="Left"
Validation.ErrorTemplate="{StaticResource ValidationTemplate}"
Style="{StaticResource TextStyleTextBox}" Margin="8,5,0,5" IsReadOnly="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=IsReadOnly}" />
I know I have to bind to a property in the parent (container view model) so I also tried this, which produces "System.Windows.Data Error: 4 : Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='CataloGo.ViewModel.PromoViewModel', AncestorLevel='1''. BindingExpression:Path=IsReadOnly; DataItem=null; target element is 'TextBox' (Name='PrintableNameEntryForm'); target property is 'IsReadOnly' (type 'Boolean')"
<TextBox Name="PrintableNameEntryForm" AutomationProperties.Name="Printable Name" Grid.Row="2" Grid.Column="1" HorizontalAlignment="Left"
Text="{Binding Path=PublicName, UpdateSourceTrigger=PropertyChanged}"
Style="{StaticResource TextStyleTextBox}" Margin="8,5,0,5"
IsReadOnly="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type localVm:PromoViewModel}}, Path=IsReadOnly}" />
But the type is the one in the error message and the DataContext of the view is set to an instance of PromoViewModel so I don't know why it cannot find the source.
This is the full view model:
public class PromoViewModel : SupervisedEntity
{
private ObservableCollection<Promo> _promos;
private Promo _current;
private bool _editMode;
private bool _isNew;
public string Title => "Promociones";
public ObservableCollection<Promo> Promos
{
get => this._promos;
set => this.SetPropertyValue(ref this._promos, value);
}
public Promo Current
{
get => this._current;
set => this.SetPropertyValue(ref this._current, value);
}
public bool IsEditMode
{
get => this._editMode;
set
{
this.SetPropertyValue(ref this._editMode, value);
this.NotifyPropertyChanged(nameof(this.IsReadOnly));
this.NotifyPropertyChanged(nameof(this.HideIfEditing));
this.NotifyPropertyChanged(nameof(this.ShowIfEditing));
}
}
public bool IsNew
{
get => this._isNew;
set => this.SetPropertyValue(ref this._isNew, value);
}
public Promo Backup { get; set; }
public bool IsReadOnly => !this.IsEditMode;
public Visibility HideIfEditing => (this.IsReadOnly ? Visibility.Visible : Visibility.Collapsed);
public Visibility ShowIfEditing => (this.IsReadOnly ? Visibility.Collapsed : Visibility.Visible);
}
And this is the corresponding view:
<Window x:Class="CataloGo.View.PromoWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:localroot="clr-namespace:CataloGo"
xmlns:local="clr-namespace:CataloGo.View"
xmlns:localVm="clr-namespace:CataloGo.ViewModel"
xmlns:localModel="clr-namespace:CataloGo.Model"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance localVm:PromoViewModel, d:IsDesignTimeCreatable=True}"
Title="{Binding Path=Title}" SizeToContent="WidthAndHeight" MinWidth="640" MinHeight="480">
<Window.Resources>
<CollectionViewSource
Source="{Binding Path=Promos}"
x:Key="PromoListingDataView" />
<DataTemplate x:Key="PromoDetailTemplate" DataType="{x:Type localModel:Promo}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="106" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0"
Style="{StaticResource SmallTitleStyle}"
Margin="5">
DescripciĆ³n:
</TextBlock>
<TextBlock Grid.Row="1" Grid.Column="0"
Style="{StaticResource SmallTitleStyle}" Margin="0,5,0,5">
Descuento (%):
</TextBlock>
<TextBox Name="PromoEntryForm" AutomationProperties.Name="Promo Description" Grid.Row="0" Grid.Column="1" HorizontalAlignment="Left"
Text="{Binding Path=FriendlyName, UpdateSourceTrigger=PropertyChanged}"
Style="{StaticResource TextStyleTextBox}" Margin="8,5,0,5"
IsReadOnly="{Binding Path=IsReadOnly}" />
<TextBox Name="DiscountEntryForm" AutomationProperties.Name="PromoDiscount" Grid.Row="1" Grid.Column="1" HorizontalAlignment="Left"
Validation.ErrorTemplate="{StaticResource ValidationTemplate}"
Style="{StaticResource TextStyleTextBox}" Margin="8,5,0,5" IsReadOnly="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=IsReadOnly}" >
<TextBox.Text>
<Binding Path="DiscountPct" StringFormat="N2" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<ExceptionValidationRule />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
</Grid>
</DataTemplate>
</Window.Resources>
<Border Padding="20">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" MinHeight="300"/>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="3"
Style="{StaticResource TitleStyle}"
Margin="5">
Promociones:
</TextBlock>
<Border Grid.Row="2" Grid.ColumnSpan="3" Style="{StaticResource BorderStyle}">
<ListView Name="ListViewPromos" Grid.Row="1" Grid.ColumnSpan="3" ItemsSource="{Binding Source={StaticResource PromoListingDataView}}"
IsSynchronizedWithCurrentItem="True"
SelectionChanged="OnSelectionChanged"
MouseDoubleClick="EnterEditMode"
IsEnabled="{Binding Path=IsReadOnly}">
<ListView.View>
<GridView AllowsColumnReorder="true" ColumnHeaderToolTip="Info de la promociĆ³n">
<GridViewColumn DisplayMemberBinding="{Binding Path=FriendlyName}" Header="Nombre de la promo" Width="300"/>
<GridViewColumn DisplayMemberBinding="{Binding Path=DiscountPct, StringFormat=N2}" Header="Descuento (%)" Width="150"/>
</GridView>
</ListView.View>
</ListView>
</Border>
<Grid Grid.Row="4" Grid.ColumnSpan="3">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ContentControl Name="PromoDetail" Grid.Row="0" Grid.ColumnSpan="2"
Content="{Binding Path=Current}"
ContentTemplate="{StaticResource PromoDetailTemplate}"
Margin="9,0,0,0" />
<StackPanel Grid.Row="1" Grid.ColumnSpan="2" Orientation="Horizontal" HorizontalAlignment="Right">
<StackPanel.Resources>
<Style TargetType="{x:Type Button}">
<Setter Property="Margin" Value="0,0,0,0"/>
</Style>
</StackPanel.Resources>
<Button Name="NewPromoButton" HorizontalAlignment="Right" Content="_Nueva" Style="{StaticResource AcctionButtonStyle}"
Click="NewPromo" Visibility="{Binding Path=HideIfEditing}"/>
<Button Name="EditPromoButton" HorizontalAlignment="Right" Content="_Modificar" Style="{StaticResource AcctionButtonStyle}"
Click="EditPromo" Visibility="{Binding Path=HideIfEditing}"/>
<Button Name="SubmitPromoButton" HorizontalAlignment="Right" Content="_Aceptar" Style="{StaticResource AcctionButtonStyle}"
Click="SubmitPromo" Visibility="{Binding Path=ShowIfEditing}"/>
<Button Name="CancelEditButton" HorizontalAlignment="Right" Content="_Cancelar" Style="{StaticResource AcctionButtonStyle}"
Click="UndoEdit" Visibility="{Binding Path=ShowIfEditing}"/>
</StackPanel>
</Grid>
</Grid>
</Border>
Use Element Binding instead of relative source.
First Provide name for the Window.
<Window x:Class="CataloGo.View.PromoWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:localroot="clr-namespace:CataloGo"
xmlns:local="clr-namespace:CataloGo.View"
xmlns:localVm="clr-namespace:CataloGo.ViewModel"
xmlns:localModel="clr-namespace:CataloGo.Model"
mc:Ignorable="d"
x:Name="Window1"
d:DataContext="{d:DesignInstance localVm:PromoViewModel, d:IsDesignTimeCreatable=True}"
Title="{Binding Path=Title}" SizeToContent="WidthAndHeight" MinWidth="640" MinHeight="480">
Element Binding Code in xaml
<TextBox Name="DiscountEntryForm" AutomationProperties.Name="PromoDiscount" Grid.Row="1" Grid.Column="1" HorizontalAlignment="Left"
Validation.ErrorTemplate="{StaticResource ValidationTemplate}"
Style="{StaticResource TextStyleTextBox}" Margin="8,5,0,5" IsReadOnly="{Binding ElementName=Window1, Path=DataContext.IsReadOnly}" >
<TextBox.Text>
<Binding Path="DiscountPct" StringFormat="N2" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<ExceptionValidationRule />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>

ProgressBar.Value strangely affects other element's position

Unless I'm missing something basic, this appears to be a bug. It is simple to reproduce. Just create a new WPF application project and paste the following in MainWindow's XAML:
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="100" Width="525"
xmlns:local="clr-namespace:WpfApplication1">
<Grid>
<ListBox>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"></Setter>
<Setter Property="IsTabStop" Value="False" />
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate DataType="{x:Type local:AudioFile}">
<Grid HorizontalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="3" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="32" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Image Width="24" Height="24" VerticalAlignment="Center" HorizontalAlignment="Left">
<Image.Source>
<BitmapImage UriSource="{Binding Icon}" />
</Image.Source>
</Image>
<TextBlock Grid.Column="1" Text="{Binding FullPath}" VerticalAlignment="Center" />
<ProgressBar Grid.Column="1" Opacity=".3" Foreground="LightGreen" Value="{Binding UploadProgress}" />
<TextBlock Grid.Column="1" HorizontalAlignment="Right" VerticalAlignment="Bottom" FontSize="8">
<Run Text="{Binding UploadRate, StringFormat={}{0:00}}" />
<Run> kbps</Run>
</TextBlock>
<Image Grid.Column="2" Width="16" Height="16" VerticalAlignment="Center" />
<ProgressBar Grid.Row="1" Grid.ColumnSpan="3" Value="{Binding PercentCompleted, Mode=OneWay}" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.Items>
<local:AudioFile FullPath="C:\audio1.mp3" Status="Uploading" UploadProgress="0" PercentCompleted="40" ErrorUploading="" />
</ListBox.Items>
</ListBox>
</Grid>
</Window>
Add a new class named AudioFile and paste the following code therein:
Public Class AudioFile
Public Property FullPath() As String
Public Property UploadProgress() As Integer
Public Property UploadRate() As Single = 120
Public Property Status() As String
Public Property ErrorUploading() As String
Public ReadOnly Property Icon() As String
Get
If String.IsNullOrEmpty(FullPath.Trim()) Then
Return ""
Else
Dim RetVal As String = "Resources/speaker.png"
Select Case System.IO.Path.GetExtension(FullPath).ToLower().Trim("."c)
Case "mp3"
RetVal = "Resources/mp3.png"
Case "wav"
RetVal = "Resources/wav.png"
End Select
Return RetVal
End If
End Get
End Property
Public Property PercentCompleted() As Integer
End Class
Now go to XAML and change the value of PercentCompleted from "40" to "80". Note the position of FullPath TextBlock as it jumps to further right. Why? The only binding we have for PercentCompleted is of ProgressBar's Value.

Textbox two way binding not triggering

I have a tab control with 3 objects, 2 lists and a textbox. The text box is bound two way :
<TabControl x:Name="tcTabs" ItemsSource="{Binding Rooms, UpdateSourceTrigger=PropertyChanged}" Margin="5" BorderThickness="1" IsSynchronizedWithCurrentItem="True">
<TabControl.ItemContainerStyle>
<Style TargetType="TabItem">
<Setter Property="Header" Value="{Binding Name}" />
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="22"/>
</Grid.RowDefinitions>
<ListBox ItemsSource="{Binding ReceivedMessages}" DisplayMemberPath="Raw" Grid.Row="0" Grid.Column="0" BorderThickness="0" />
<ListBox ItemsSource="{Binding Users}" DisplayMemberPath="Nick" Visibility="{Binding Type, Converter={StaticResource UserListVisibilityConverter}}" Grid.Row="0" Grid.Column="1" BorderThickness="1,0,0,0" BorderBrush="#FFBBBBBB" Width="130" />
<TextBox Text="{Binding CurrentInput, Mode="TwoWay"}" Grid.Row="1" Grid.ColumnSpan="2" BorderThickness="0,1,0,0" BorderBrush="#FFBBBBBB" Height="22" />
</Grid>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</TabControl.ItemContainerStyle>
</TabControl>
Backing object :
public string CurrentInput
{
get
{
return _currentInput;
}
set
{
if (value != _currentInput)
{
_currentInput = value;
OnPropertyChanged();
}
}
}
Problem is, when I change the text and click another tab it does not update the backing field (does not even hit the setter), however if I change then click the listbox it does...
Any reason for this odd behaviour?
That is not an odd behaviour and has been asked multiple times before. Read about Binding.UpdateSourceTrigger, also see the remarks of the respective property you bind.
I've solve this problem (Twoway Binding) by manual trigger the databinding engine using
DataContext = this;

How to automatically scale font size for a group of controls?

I have a few TextBlocks in WPF in a Grid that I would like to scale depending on their available width / height. When I searched for automatically scaling Font size the typical suggestion is to put the TextBlock into a ViewBox.
So I did this:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Viewbox MaxHeight="18" Grid.Column="0" Stretch="Uniform" Margin="5" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<TextBlock Text="{Binding Text1}" />
</Viewbox>
<Viewbox MaxHeight="18" Grid.Column="1" Stretch="Uniform" Margin="5" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<TextBlock Text="{Binding Text2}" />
</Viewbox>
<Viewbox MaxHeight="18" Grid.Column="2" Stretch="Uniform" Margin="5" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<TextBlock Text="{Binding Text3}" />
</Viewbox>
</Grid>
And it scales the font for each TextBlock automatically. However, this looks funny because if one of the TextBlocks has longer text then it will be in a smaller font while it's neighboring grid elements will be in a larger font. I want the Font size to scale by group, perhaps it would be nice if I could specify a "SharedSizeGroup" for a set of controls to auto size their font.
e.g.
The first text blocks text might be "3/26/2013 10:45:30 AM", and the second TextBlocks text might say "FileName.ext". If these are across the width of a window, and the user begins resizing the window smaller and smaller. The date will start making its font smaller than the file name, depending on the length of the file name.
Ideally, once one of the text fields starts to resize the font point size, they would all match. Has anyone came up with a solution for this or can give me a shot at how you would make it work? If it requires custom code then hopefully we / I could repackage it into a custom Blend or Attached Behavior so that is re-usable for the future. I think it is a pretty general problem, but I wasn't able to find anything on it by searching.
Update
I tried Mathieu's suggestion and it sort of works, but it has some side-effects:
<Window x:Class="WpfApplication6.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="270" Width="522">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Rectangle Grid.Row="0" Fill="SkyBlue" />
<Viewbox Grid.Row="1" MaxHeight="30" Stretch="Uniform" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" >
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="col"/>
<ColumnDefinition Width="Auto" SharedSizeGroup="col"/>
<ColumnDefinition Width="Auto" SharedSizeGroup="col"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="SomeLongText" Margin="5" />
<TextBlock Grid.Column="1" Text="TextA" Margin="5" />
<TextBlock Grid.Column="2" Text="TextB" Margin="5" />
</Grid>
</Viewbox>
</Grid>
</Window>
Honestly, missing hte proportional columns is probably fine with me. I wouldn't mind it AutoSizing the columns to make smart use of the space, but it has to span the entire width of the window.
Notice without maxsize, in this extended example the text is too large:
<Window x:Class="WpfApplication6.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="270" Width="522">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Rectangle Grid.Row="0" Fill="SkyBlue" />
<Viewbox Grid.Row="1" Stretch="Uniform" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" >
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="col"/>
<ColumnDefinition Width="Auto" SharedSizeGroup="col"/>
<ColumnDefinition Width="Auto" SharedSizeGroup="col"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="SomeLongText" Margin="5" />
<TextBlock Grid.Column="1" Text="TextA" Margin="5" />
<TextBlock Grid.Column="2" Text="TextB" Margin="5" />
</Grid>
</Viewbox>
</Grid>
Here, I would want to limit how big the font can get, so it doesn't waste vertical window real estate. I'm expecting the output to be aligned left, center, and right with the Font being as big as possible up to the desired maximum size.
#adabyron
The solution you propose is not bad (And is the best yet) but it does have some limitations. For example, initially I wanted my columns to be proportional (2nd one should be centered). For example, my TextBlocks might be labeling the start, center, and stop of a graph where alignment matters.
<Window x:Class="WpfApplication6.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:b="clr-namespace:WpfApplication6.Behavior"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Rectangle Grid.Row="0" Fill="SkyBlue" />
<Line X1="0.5" X2="0.5" Y1="0" Y2="1" Stretch="Fill" StrokeThickness="3" Stroke="Red" />
<Grid Grid.Row="1">
<i:Interaction.Behaviors>
<b:MoveToViewboxBehavior />
</i:Interaction.Behaviors>
<Viewbox Stretch="Uniform" />
<ContentPresenter >
<ContentPresenter.Content>
<Grid x:Name="TextBlockContainer">
<Grid.Resources>
<Style TargetType="TextBlock" >
<Setter Property="FontSize" Value="16" />
<Setter Property="Margin" Value="5" />
</Style>
</Grid.Resources>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="SomeLongText" VerticalAlignment="Center" HorizontalAlignment="Center" />
<TextBlock Grid.Column="2" Text="TextA" HorizontalAlignment="Center" VerticalAlignment="Center" />
<TextBlock Grid.Column="4" Text="TextB" HorizontalAlignment="Center" VerticalAlignment="Center" />
</Grid>
</ContentPresenter.Content>
</ContentPresenter>
</Grid>
</Grid>
</Window>
And here is the result. Notice it does not know that it is getting clipped early on, and then when it substitutes ViewBox it looks as if the Grid defaults to column size "Auto" and no longer aligns center.
I wanted to edit the answer I had already offered, but then decided it makes more sense to post a new one, because it really depends on the requirements which one I'd prefer. This here probably fits Alan's idea better, because
The middle textblock stays in the middle of the window
Fontsize adjustment due to height clipping is accomodated
Quite a bit more generic
No viewbox involved
The other one has the advantage that
Space for the textblocks is allocated more efficiently (no unnecessary margins)
Textblocks may have different fontsizes
I tested this solution also in a top container of type StackPanel/DockPanel, behaved decently.
Note that by playing around with the column/row widths/heights (auto/starsized), you can get different behaviors. So it would also be possible to have all three textblock columns starsized, but that means width clipping does occur earlier and there is more margin. Or if the row the grid resides in is auto sized, height clipping will never occur.
Xaml:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:beh="clr-namespace:WpfApplication1.Behavior"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="0.9*"/>
<RowDefinition Height="0.1*" />
</Grid.RowDefinitions>
<Rectangle Fill="DarkOrange" />
<Grid x:Name="TextBlockContainer" Grid.Row="1" >
<i:Interaction.Behaviors>
<beh:ScaleFontBehavior MaxFontSize="32" />
</i:Interaction.Behaviors>
<Grid.Resources>
<Style TargetType="TextBlock" >
<Setter Property="Margin" Value="5" />
<Setter Property="VerticalAlignment" Value="Center" />
</Style>
</Grid.Resources>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="SomeLongText" />
<TextBlock Grid.Column="1" Text="TextA" HorizontalAlignment="Center" />
<TextBlock Grid.Column="2" Text="TextB" HorizontalAlignment="Right" />
</Grid>
</Grid>
</Window>
ScaleFontBehavior:
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interactivity;
using System.Windows.Media;
using WpfApplication1.Helpers;
namespace WpfApplication1.Behavior
{
public class ScaleFontBehavior : Behavior<Grid>
{
// MaxFontSize
public double MaxFontSize { get { return (double)GetValue(MaxFontSizeProperty); } set { SetValue(MaxFontSizeProperty, value); } }
public static readonly DependencyProperty MaxFontSizeProperty = DependencyProperty.Register("MaxFontSize", typeof(double), typeof(ScaleFontBehavior), new PropertyMetadata(20d));
protected override void OnAttached()
{
this.AssociatedObject.SizeChanged += (s, e) => { CalculateFontSize(); };
}
private void CalculateFontSize()
{
double fontSize = this.MaxFontSize;
List<TextBlock> tbs = VisualHelper.FindVisualChildren<TextBlock>(this.AssociatedObject);
// get grid height (if limited)
double gridHeight = double.MaxValue;
Grid parentGrid = VisualHelper.FindUpVisualTree<Grid>(this.AssociatedObject.Parent);
if (parentGrid != null)
{
RowDefinition row = parentGrid.RowDefinitions[Grid.GetRow(this.AssociatedObject)];
gridHeight = row.Height == GridLength.Auto ? double.MaxValue : this.AssociatedObject.ActualHeight;
}
foreach (var tb in tbs)
{
// get desired size with fontsize = MaxFontSize
Size desiredSize = MeasureText(tb);
double widthMargins = tb.Margin.Left + tb.Margin.Right;
double heightMargins = tb.Margin.Top + tb.Margin.Bottom;
double desiredHeight = desiredSize.Height + heightMargins;
double desiredWidth = desiredSize.Width + widthMargins;
// adjust fontsize if text would be clipped vertically
if (gridHeight < desiredHeight)
{
double factor = (desiredHeight - heightMargins) / (this.AssociatedObject.ActualHeight - heightMargins);
fontSize = Math.Min(fontSize, MaxFontSize / factor);
}
// get column width (if limited)
ColumnDefinition col = this.AssociatedObject.ColumnDefinitions[Grid.GetColumn(tb)];
double colWidth = col.Width == GridLength.Auto ? double.MaxValue : col.ActualWidth;
// adjust fontsize if text would be clipped horizontally
if (colWidth < desiredWidth)
{
double factor = (desiredWidth - widthMargins) / (col.ActualWidth - widthMargins);
fontSize = Math.Min(fontSize, MaxFontSize / factor);
}
}
// apply fontsize (always equal fontsizes)
foreach (var tb in tbs)
{
tb.FontSize = fontSize;
}
}
// Measures text size of textblock
private Size MeasureText(TextBlock tb)
{
var formattedText = new FormattedText(tb.Text, CultureInfo.CurrentUICulture,
FlowDirection.LeftToRight,
new Typeface(tb.FontFamily, tb.FontStyle, tb.FontWeight, tb.FontStretch),
this.MaxFontSize, Brushes.Black); // always uses MaxFontSize for desiredSize
return new Size(formattedText.Width, formattedText.Height);
}
}
}
VisualHelper:
public static List<T> FindVisualChildren<T>(DependencyObject obj) where T : DependencyObject
{
List<T> children = new List<T>();
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
{
var o = VisualTreeHelper.GetChild(obj, i);
if (o != null)
{
if (o is T)
children.Add((T)o);
children.AddRange(FindVisualChildren<T>(o)); // recursive
}
}
return children;
}
public static T FindUpVisualTree<T>(DependencyObject initial) where T : DependencyObject
{
DependencyObject current = initial;
while (current != null && current.GetType() != typeof(T))
{
current = VisualTreeHelper.GetParent(current);
}
return current as T;
}
Put your grid in the ViewBox, which will scale the whole Grid :
<Viewbox Stretch="Uniform" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding Text1}" Margin="5" />
<TextBlock Grid.Column="1" Text="{Binding Text2}" Margin="5" />
<TextBlock Grid.Column="2" Text="{Binding Text3}" Margin="5" />
</Grid>
</Viewbox>
I think I know the way to go and will leave the rest to you. In this example, I bound the FontSize to the ActualHeight of the TextBlock, using a converter (the converter is below):
<Window x:Class="MyNamespace.Test"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:Converters="clr-namespace:UpdateYeti.Converters"
Title="Test" Height="570" Width="522">
<Grid Height="370" Width="522">
<Grid.Resources>
<Converters:HeightToFontSizeConverter x:Key="conv" />
</Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Rectangle Grid.Row="0" Fill="SkyBlue" />
<Grid Grid.Row="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" MinHeight="60" Background="Beige">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="SomeLongText" Margin="5"
FontSize="{Binding RelativeSource={RelativeSource Self}, Path=ActualHeight, Converter={StaticResource conv}}" />
<TextBlock Grid.Column="1" Text="TextA" Margin="5" HorizontalAlignment="Center"
FontSize="{Binding RelativeSource={RelativeSource Self}, Path=ActualHeight, Converter={StaticResource conv}}" />
<TextBlock Grid.Column="2" Text="TextB" Margin="5" FontSize="{Binding RelativeSource={RelativeSource Self}, Path=ActualHeight, Converter={StaticResource conv}}" />
</Grid>
</Grid>
</Window>
[ValueConversion(typeof(double), typeof(double))]
class HeightToFontSizeConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
// here you can use the parameter that you can give in here via setting , ConverterParameter='something'} or use any nice login with the VisualTreeHelper to make a better return value, or maybe even just hardcode some max values if you like
var height = (double)value;
return .65 * height;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
General remark: A possible alternative to the whole text scaling could be to just use TextTrimming on the TextBlocks.
I've struggled to find a solution to this one. Using a viewbox is really hard to mix with any layout adjustments. Worst of all, ActualWidth etc. do not change inside a viewbox. So I finally decided to use the viewbox only if absolutely necessary, which is when clipping would occur. I'm therefore moving the content between a ContentPresenter and a Viewbox, depending upon the available space.
This solution is not as generic as I would like, mainly the MoveToViewboxBehavior does assume it is attached to a grid with the following structure. If that cannot be accomodated, the behavior will most likely have to be adjusted. Creating a usercontrol and denoting the necessary parts (PART_...) might be a valid alternative.
Note that I have extended the grid's columns from three to five, because that makes the solution a lot easier. It means that the middle textblock will not be exactly in the middle, in the sense of absolute coordinates, instead it is centered between the textblocks to the left and right.
<Grid > <!-- MoveToViewboxBehavior attached to this grid -->
<Viewbox />
<ContentPresenter>
<ContentPresenter.Content>
<Grid x:Name="TextBlockContainer">
<TextBlocks ... />
</Grid>
</ContentPresenter.Content>
</ContentPresenter>
</Grid>
Xaml:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:beh="clr-namespace:WpfApplication1.Behavior"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Rectangle Grid.Row="0" Fill="SkyBlue" />
<Grid Grid.Row="1">
<i:Interaction.Behaviors>
<beh:MoveToViewboxBehavior />
</i:Interaction.Behaviors>
<Viewbox Stretch="Uniform" />
<ContentPresenter >
<ContentPresenter.Content>
<Grid x:Name="TextBlockContainer">
<Grid.Resources>
<Style TargetType="TextBlock" >
<Setter Property="FontSize" Value="16" />
<Setter Property="Margin" Value="5" />
</Style>
</Grid.Resources>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="SomeLongText" />
<TextBlock Grid.Column="2" Text="TextA" />
<TextBlock Grid.Column="4" Text="TextB" />
</Grid>
</ContentPresenter.Content>
</ContentPresenter>
</Grid>
</Grid>
</Window>
MoveToViewBoxBehavior:
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interactivity;
using System.Windows.Media;
using WpfApplication1.Helpers;
namespace WpfApplication1.Behavior
{
public class MoveToViewboxBehavior : Behavior<Grid>
{
// IsClipped
public bool IsClipped { get { return (bool)GetValue(IsClippedProperty); } set { SetValue(IsClippedProperty, value); } }
public static readonly DependencyProperty IsClippedProperty = DependencyProperty.Register("IsClipped", typeof(bool), typeof(MoveToViewboxBehavior), new PropertyMetadata(false, OnIsClippedChanged));
private static void OnIsClippedChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var beh = (MoveToViewboxBehavior)sender;
Grid grid = beh.AssociatedObject;
Viewbox vb = VisualHelper.FindVisualChild<Viewbox>(grid);
ContentPresenter cp = VisualHelper.FindVisualChild<ContentPresenter>(grid);
if ((bool)e.NewValue)
{
// is clipped, so move content to Viewbox
UIElement element = cp.Content as UIElement;
cp.Content = null;
vb.Child = element;
}
else
{
// can be shown without clipping, so move content to ContentPresenter
cp.Content = vb.Child;
vb.Child = null;
}
}
protected override void OnAttached()
{
this.AssociatedObject.SizeChanged += (s, e) => { IsClipped = CalculateIsClipped(); };
}
// Determines if the width of all textblocks within TextBlockContainer (using MaxFontSize) are wider than the AssociatedObject grid
private bool CalculateIsClipped()
{
double totalDesiredWidth = 0d;
Grid grid = VisualHelper.FindVisualChildByName<Grid>(this.AssociatedObject, "TextBlockContainer");
List<TextBlock> tbs = VisualHelper.FindVisualChildren<TextBlock>(grid);
foreach (var tb in tbs)
{
if (tb.TextWrapping != TextWrapping.NoWrap)
return false;
totalDesiredWidth += MeasureText(tb).Width + tb.Margin.Left + tb.Margin.Right + tb.Padding.Left + tb.Padding.Right;
}
return Math.Round(this.AssociatedObject.ActualWidth, 5) < Math.Round(totalDesiredWidth, 5);
}
// Measures text size of textblock
private Size MeasureText(TextBlock tb)
{
var formattedText = new FormattedText(tb.Text, CultureInfo.CurrentUICulture,
FlowDirection.LeftToRight,
new Typeface(tb.FontFamily, tb.FontStyle, tb.FontWeight, tb.FontStretch),
tb.FontSize, Brushes.Black);
return new Size(formattedText.Width, formattedText.Height);
}
}
}
VisualHelper:
public static class VisualHelper
{
public static T FindVisualChild<T>(DependencyObject obj) where T : DependencyObject
{
T child = null;
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
{
var o = VisualTreeHelper.GetChild(obj, i);
if (o != null)
{
child = o as T;
if (child != null) break;
else
{
child = FindVisualChild<T>(o); // recursive
if (child != null) break;
}
}
}
return child;
}
public static List<T> FindVisualChildren<T>(DependencyObject obj) where T : DependencyObject
{
List<T> children = new List<T>();
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
{
var o = VisualTreeHelper.GetChild(obj, i);
if (o != null)
{
if (o is T)
children.Add((T)o);
children.AddRange(FindVisualChildren<T>(o)); // recursive
}
}
return children;
}
public static T FindVisualChildByName<T>(DependencyObject parent, string name) where T : FrameworkElement
{
T child = default(T);
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
{
var o = VisualTreeHelper.GetChild(parent, i);
if (o != null)
{
child = o as T;
if (child != null && child.Name == name)
break;
else
child = FindVisualChildByName<T>(o, name);
if (child != null) break;
}
}
return child;
}
}
You can use a hidden ItemsControl in a ViewBox.
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Viewbox VerticalAlignment="Bottom">
<Grid>
<TextBlock Text="SomeLongText"/>
<ItemsControl Visibility="Hidden">
<ItemsPanelTemplate>
<Grid/>
</ItemsPanelTemplate>
<TextBlock Text="SomeLongText"/>
<TextBlock Text="TextA"/>
<TextBlock Text="TextB"/>
</ItemsControl>
</Grid>
</Viewbox>
<Viewbox Grid.Column="1" VerticalAlignment="Bottom">
<Grid>
<TextBlock Text="TextA"/>
<ItemsControl Visibility="Hidden">
<ItemsPanelTemplate>
<Grid/>
</ItemsPanelTemplate>
<TextBlock Text="SomeLongText"/>
<TextBlock Text="TextA"/>
<TextBlock Text="TextB"/>
</ItemsControl>
</Grid>
</Viewbox>
<Viewbox Grid.Column="2" VerticalAlignment="Bottom">
<Grid>
<TextBlock Text="TextB"/>
<ItemsControl Visibility="Hidden">
<ItemsPanelTemplate>
<Grid/>
</ItemsPanelTemplate>
<TextBlock Text="SomeLongText"/>
<TextBlock Text="TextA"/>
<TextBlock Text="TextB"/>
</ItemsControl>
</Grid>
</Viewbox>
</Grid>
or
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Viewbox VerticalAlignment="Bottom">
<Grid>
<TextBlock Text="{Binding Text1}"/>
<ItemsControl Visibility="Hidden" ItemsSource="{Binding AllText}">
<ItemsPanelTemplate>
<Grid/>
</ItemsPanelTemplate>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Viewbox>
<Viewbox Grid.Column="1" VerticalAlignment="Bottom">
<Grid>
<TextBlock Text="{Binding Text2}"/>
<ItemsControl Visibility="Hidden" ItemsSource="{Binding AllText}">
<ItemsPanelTemplate>
<Grid/>
</ItemsPanelTemplate>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Viewbox>
<Viewbox Grid.Column="2" VerticalAlignment="Bottom">
<Grid>
<TextBlock Text="{Binding Text3}"/>
<ItemsControl Visibility="Hidden" ItemsSource="{Binding AllText}">
<ItemsPanelTemplate>
<Grid/>
</ItemsPanelTemplate>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Viewbox>
</Grid>
A solution could be something like that :
Choose a maxFontSize, then define the appropriate FontSize to be displayed considering the current Window by using a linear equation. Window's height or width would limit the final FontSize choice.
Let's take the case of a "single kind TextBlock" for the whole grid :
Window.Current.SizeChanged += (sender, args) =>
{
int minFontSize = a;
int maxFontSize = b;
int maxMinFontSizeDiff = maxFontSize - minFontSize;
int gridMinHeight = c;
int gridMaxHeight = d;
int gridMaxMinHeightDiff = gridMaxHeight - gridMinHeight;
int gridMinWidth = e;
int gridMaxWidth = f;
int gridMaxMinHeightDiff = gridMaxWidth - gridMaxWidth;
//Linear equation considering "max/min FontSize" and "max/min GridHeight/GridWidth"
double heightFontSizeDouble = (maxMinFontSizeDiff / gridMaxMinHeightDiff ) * Grid.ActualHeight + (maxFontSize - (gridMaxHeight * (maxMinFontSizeDiff / gridMaxMinHeightDiff)))
double widthFontSizeDouble = (maxMinFontSizeDiff / gridMaxMinWidthDiff ) * Grid.ActualWidth + (maxFontSize - (gridMaxWidth * (maxMinFontSizeDiff / gridMaxMinWidthDiff)))
int heightFontSize = (int)Math.Round(heightFontSizeDouble)
int widthFontSize = (int)Math.Round(widthFontSizeDouble)
foreach (var children in Grid.Children)
{
(children as TextBlock).FontSize = Math.Min(heightFontSize, widthFontSize);
}
}
I use ScaleTransform. This scales the current font by the desired amount:
<ScaleTransform x:Key="FontDoubled" ScaleX="2" ScaleY="2" />
<ScaleTransform x:Key="FontHalved" ScaleX="0.5" ScaleY="0.5" />
The larger the ScaleX and ScaleY, the larger the font is scaled by. No need to change individual font sizes.
It can also be used with just ScaleX or ScaleY to change the font size in one direction only, or with different values.
To use it:
<Label Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="4"
FontWeight="Bold"
Content="This is normal the size" />
...gives:
And:
<Label Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="4"
FontWeight="Bold"
LayoutTransform="{StaticResource FontDoubled}"
Content="This is Double size" />
...gives:

MVVM + UserControl + UserControl + DependencyProperty

I have a user control which displays addresses housed inside of another page that is bound to a viewmodel. The viewmodel has a primitive User which has a collection of Address objects. The User control will reside on several pages so I would like to be able to bind it to the address list via a dependency property. While my current solution is working, something about it just doesn't feel right and I thought I'd ask for a second opinion. I have chopped out a lot of code for brevity's sake.
Basically the page binds to the dependency property in the usercontrols code behind which then updates the usercontrol's datagrid by setting it's itemsource. This seems to me to break the basic tenants of MVVM.
AddressListView control:
<UserControl x:Class="Insight.Controls.AddressListView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"
xmlns:tk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/toolkit"
xmlns:command="clr-namespace:PrismFramework.Implementors.Commanding;assembly=PrismFramework"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="840">
<UserControl.Resources>
<command:ObservableCommand x:Name="EditAddressCommand" Value="{Binding EditAddressCmd}"/>
<command:ObservableCommand x:Name="DeleteAddressCommand" Value="{Binding DeleteAddressCmd}"/>
</UserControl.Resources>
<Grid x:Name="LayoutRoot" Background="White">
<sdk:DataGrid Name="dgAddresses"
Height="Auto"
Width="Auto"
AutoGenerateColumns="False"
HeadersVisibility="None" >
<sdk:DataGrid.Columns>
<sdk:DataGridTemplateColumn x:Name="dgcAddresses"
Width="*" >
<sdk:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Border x:Name="bdrAddress"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Height="Auto"
Width="Auto"
BorderBrush="Silver"
BorderThickness="1"
Padding="0"
Margin="1,1,1,1">
<Grid x:Name="grdAddressItem"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Height="Auto"
Width="Auto">
<Grid.RowDefinitions>
<RowDefinition Height="17" MinHeight="17"/>
<RowDefinition Height="17" MinHeight="17"/>
<RowDefinition Height="17" MinHeight="17"/>
<RowDefinition Height="17" MinHeight="17"/>
<RowDefinition Height="17" MinHeight="17"/>
<RowDefinition Height="17" MinHeight="17"/>
<RowDefinition Height="17" MinHeight="17"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="70"/>
<ColumnDefinition Width="55" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock Padding="0,0,5,0" Text="Type:" TextAlignment="Right" />
<TextBlock Grid.Column="1" Padding="0" Text="{Binding Path=AType}" Grid.ColumnSpan="2" />
<TextBlock Grid.Row ="1" Grid.Column="0" Padding="0,0,5,0" Text="Address 1:" TextAlignment="Right" />
<!-- List Of Similar Fields ->
<Grid x:Name="grdAddressEditOptions"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Height="Auto"
Width="Auto"
Grid.Column="3"
Grid.RowSpan="7" >
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Button x:Name="btnEdit"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Height="Auto"
Width="Auto"
Grid.Row="0"
Padding="4,5,4,8"
Margin="0,8,10,0"
Command="{Binding Value, Source={StaticResource EditAddressCommand}}"
CommandParameter="{Binding}" >
<Button.Content>
<Image x:Name="btnEditIcon"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Height="Auto"
Width="Auto"
Source="/Insight.ModuleUser;component/Images/edit.png"
Visibility="Visible" />
</Button.Content>
</Button>
<Button x:Name="btnDelete"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Height="Auto"
Width="Auto"
Grid.Row="2"
Padding="4,5,4,8"
Margin="0,0,10,5"
Command="{Binding Value, Source={StaticResource DeleteAddressCommand}}"
CommandParameter="{Binding}" >
<Button.Content>
<Image x:Name="btnDeleteIcon"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Height="Auto"
Width="Auto"
Source="/Insight.ModuleUser;component/Images/delete.png"
Visibility="Visible" />
</Button.Content>
</Button>
</Grid>
</Grid>
</Border>
</DataTemplate>
</sdk:DataGridTemplateColumn.CellTemplate>
</sdk:DataGridTemplateColumn>
</sdk:DataGrid.Columns>
</sdk:DataGrid>
</Grid>
</UserControl>
AddressListView code behind:
Imports System.Collections.ObjectModel
Imports Insight.DataServices.Primitives
Partial Public Class AddressListView
Inherits UserControl
Public ReadOnly AddressesProperty As DependencyProperty = DependencyProperty.Register("Addresses", GetType(ObservableCollection(Of Address)), GetType(AddressListView), New PropertyMetadata(Nothing, New PropertyChangedCallback(AddressOf OnAddressesChanged)))
Public Sub New()
InitializeComponent()
End Sub
Public Property Addresses As ObservableCollection(Of Address)
Get
Return DirectCast(GetValue(AddressesProperty), ObservableCollection(Of Address))
End Get
Set(value As ObservableCollection(Of Address))
SetValue(AddressesProperty, value)
End Set
End Property
Public Sub OnAddressesChanged()
Me.dgAddresses.ItemsSource = Addresses
End Sub
End Class
Base page:
<UserControl x:Class="Insight.ModuleUser.Views.EditUserView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"
xmlns:prism="clr-namespace:Microsoft.Practices.Prism.Interactivity.InteractionRequest;assembly=Microsoft.Practices.Prism.Interactivity"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:cm="clr-namespace:System.ComponentModel;assembly=System.Windows"
xmlns:data="clr-namespace:System.Windows.Data;assembly=System.Windows"
xmlns:vm="clr-namespace:Insight.ModuleUser.ViewModels"
xmlns:command="clr-namespace:PrismFramework.Implementors.Commanding;assembly=PrismFramework"
xmlns:controls="clr-namespace:Insight.Controls;assembly=Insight.Controls"
xmlns:modalDialogs="clr-namespace:Insight.Controls.ModalDialogViews;assembly=Insight.Controls"
mc:Ignorable="d"
d:DesignHeight="500" d:DesignWidth="1144"
d:DataContext="{d:DesignData /Insight.ModuleUser;component/SampleData/EditUserViewModelSampleData.xaml}">
<UserControl.Resources>
<command:ObservableCommand x:Name="OpenProjectCommand" Value="{Binding OpenProjectCmd}"/>
<command:ObservableCommand x:Name="OpenPaymentCommand" Value="{Binding OpenPaymentCmd}"/>
<command:ObservableCommand x:Name="OpenInvoiceCommand" Value="{Binding OpenInvoiceCmd}"/>
<command:ObservableCommand x:Name="OpenPaymentItemCommand" Value="{Binding OpenPaymentItemCmd}"/>
<command:ObservableCommand x:Name="EditPhoneCommand" Value="{Binding EditPhoneNumberCmd}"/>
<command:ObservableCommand x:Name="DeletePhoneCommand" Value="{Binding DeletePhoneNumberCmd}"/>
<command:ObservableCommand x:Name="EditEmailAddressCommand" Value="{Binding EditEmailAddressCmd}"/>
<command:ObservableCommand x:Name="DeleteEmailAddressCommand" Value="{Binding DeleteEmailAddressCmd}"/>
</UserControl.Resources>
<Grid x:Name="LayoutRoot" >
<controls:AddressListView x:Name="ctrlAddressListView"
Addresses="{Binding User.Addresses}" />
</Grid>
</UserControl>
This seems a perfectly reasonable approach. However, you could use binding in your user control view, rather than setting the items source in code.
To do this you need to set the DataContext of the user control to be your user control type. This could be done either in the code behind for the user control (setting this.DataContext = this), or through element binding in XAML:
<UserControl
...
x:Name="MyName"
DataContext="{Binding ElementName=MyName}"
However, my approach would be not to use a user control at all, as all you're really talking about is view composition and reusing a particular section of the view between other views.
View composition is extremely straightforward with an MVVM framework such as Caliburn.Micro. In this case you would have an AddressViewModel and AddressView, and use a ContentControl to inject the AddressView into the base view:
<ContentControl x:Name="AddressViewModel" />

Resources