Missing value on UI after deserialization using XamlReader.Load - c#

A few short explanations before final question.
I have to clone different UserControls and Panels in my WPF app. I decided to use next approach:
xamlString = XamlWriter.Save(control.Content);
stringReader = new StringReader(xamlString);
xmlReader = XmlReader.Create(stringReader);
restoredVisual = (Visual)XamlReader.Load(xmlReader);
Also I have next class:
public class UnitLabel : Label
{
public string Unit
{
get { return (string )GetValue(UnitProperty); }
set { SetValue(UnitProperty, value); }
}
public static readonly DependencyProperty UnitProperty = DependencyProperty.Register("Unit", typeof(string), typeof(UnitLabel), new PropertyMetadata(""));
}
And template for this class
<ControlTemplate x:Key="template" TargetType="local:UnitLabel">
<StackPanel Orientation="Horizontal">
<ContentPresenter />
<ContentPresenter Content="{TemplateBinding Unit}" />
</StackPanel>
</ControlTemplate>
Question: Unit string is not visible after deserialization but property Unit has correct value. Do you have any ideas how to fix the issue?
I have found the reason and understood that my question is really stupid. Controls lose thier style and templates after deserialization. I can set style and it solves my problem.

Controls lose their styles and templates after deserialization. I can set style and it solves my problem.

Related

In C# XAML for Windows 8.1 how do I bind styles to a variable?

I've been banging my head against this one for quite some time now, so I figured I'd ask here and see if someone can give me a hand. First off, I'm new to C#, XAML, and Windows 8 programming.
The basic thing I want to do is this: based on a change to the data model (which I'm doing in my little testbed project inside a Button_Click handler), I'd like to change the Foreground value in a <Style>. I have had success in changing the Foreground value in a <TextBlock>, but not on a <Style> that operates on another <TextBlock>.
Here's what I've done (pardon the verbosity, hopefully it makes sense):
I've got a class called MyColorDataSource that I'm using as a binding target in my MyPage.xaml page. Here's the relevant code from that class:
public class MyColorDataSource
{
private MyColors _brush1;
public MyColors Brush1
{
get { return _brush1; }
set { _brush1 = value; }
}
private MyColors _brush2;
public MyColors Brush2
{
get { return _brush2; }
set { _brush2 = value; }
}
public MyColorDataSource()
{
_brush1 = new MyColors();
_brush2 = new MyColors();
_brush1.BrushColor= new SolidColorBrush(Colors.DarkRed);
_brush2.BrushColor = new SolidColorBrush(Colors.Pink);
}
}
You'll notice that the variables in MyColorDataSource are of type MyColors. Here's the definition of that class:
public class MyColors : INotifyPropertyChanged
{
private SolidColorBrush _brushColor;
public SolidColorBrush BrushColor
{
get { return _brushColor; }
set {
if (value != _brushColor)
{
_brushColor = value;
NotifyPropertyChanged("BrushColor");
}
}
}
#region INotifyPropertyChanged Members
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
Debug.WriteLine("in NotifyPropertyChanged for " + propertyName);
}
}
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
Here's the XAML I've written, with two <TextBlock> elements, one of which has its Foreground property bound directly to the datasource, and one of which uses a <Style> which I'm trying to bind to the datasource. In MyPage.xaml:
<Page.Resources>
<Style TargetType="TextBlock" x:Key="BoundColor">
<Setter Property="Foreground" Value="{Binding Brush2.BrushColor}" />
</Style>
</Page.Resources>
<StackPanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<TextBlock x:Name="TextBlockToFormatWithBinding" Text="Direct Binding" Foreground="{Binding BrushColor}" />
<TextBlock x:Name="TextBlockToFormatWithStyleBinding" Text="Binding through style" Style="{StaticResource BoundColor}"/>
</StackPanel>
I've set up my DataSource variable in App.xaml.cs:
public sealed partial class App : Application
{
public static MyColorDataSource ColorDataSource;
public App()
{
this.InitializeComponent();
this.Suspending += this.OnSuspending;
ColorDataSource = new MyColorDataSource();
}
}
And I access the datasource in MyPage.xaml.cs:
public MyPage()
{
this.InitializeComponent();
App.ColorDataSource.Brush1.BrushColor = new SolidColorBrush(Colors.DarkOliveGreen);
App.ColorDataSource.Brush2.BrushColor = new SolidColorBrush(Colors.DarkKhaki);
this.DataContext = App.ColorDataSource;
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
TextBlockToFormatWithBinding.DataContext = App.ColorDataSource.Brush1;
// TextBlockToFormatWithStyleBinding.DataContext = App.ColorDataSource.Brush2; // tried this, it did nothing
}
If you're still with me, thank you! What's happening is that the first <TextBlock> shows up as expected, in a DarkOliveGreen color. The second <TextBlock> does not show up at all, and I'm assuming that its Foreground is defaulting to Black (the page background is also Black, so I can't see it). When I change the Foreground value in the <Style> to something like "DarkRed", voila, it shows up as expected. So the <TextBlock> is definitely there, but it's not getting its Foreground value from the DataModel as I want it to.
Has anyone out there run into this before, and if so, how did you resolve it? I really don't want to have to change each <TextBlock> individually, as I intend to try to style items in a <ListView>, and I really don't want to iterate over all of the <Item>s and change their styling one by one. Having done a lot of HTML/CSS/jQuery, I'm used to being able to style things by class instead of iteratively, one at a time.
Thanks in advance for any help or advice you can offer.
Edit:
I did manage to run across a technique that gets me pretty close to what I'm trying to do here, partially with the help of the answer below (thanks again for that!). Hopefully if someone else comes along with a similar question, this will help out.
<SolidColorBrush x:Key="myBrush" Color="#330000FF" />
<Style TargetType="TextBlock" x:Key="BoundColor">
<Setter Property="Foreground" Value="{StaticResource myBrush}" />
</Style>
Then in my code-behind, I can do something like this:
((SolidColorBrush)Resources["myBrush"]).Color = Color.FromArgb(64, 255, 0, 0);
This doesn't put the color of the element in the data model, per se, but honestly does the color of an element really belong in the data model? I would generally say that it does not.
Note that I didn't have any luck making this technique work on the 'Visibility' <Setter> I tried to put on it (I wanted the TextBlock to be hidden sometimes and visible sometimes), but for colors and other object-based <Setter>s it worked just fine. My supposition based upon the little that I know is that this is true because Visibility is an enum, not class. So while I can put this in my <Page.Resources>:
<Visibility x:Key="TextBlockVisibility">0</Visibility>
and this in the <Style>:
<Setter Property="Visibility" Value="{StaticResource TextBlockVisibility}" />
I don't seem to be able to alter the value of the TextBlockVisibility in such a way that the <Style> will notice or be affected by the change in value.
Sorry, but you can't. Like a number of very useful WPF features (your code works just fine in a WPF program), this is AFAIK not supported on Windows Phone.
That said, you can work around the problem. The most obvious solution is to not use a style in the first place. Another solution is to use a "helper" object as the actual setter value in your style, which itself manages the binding via an attached property being set by the style's setter.
An example of this technique can be found at David Anson's web site, here:
The taming of the phone [New SetterValueBindingHelper sample demonstrates its usefulness on Windows Phone 7 (and Silverlight 4)]
Using that example, your style setter might look something like this:
<Setter Property="delay:SetterValueBindingHelper.PropertyBinding">
<Setter.Value>
<delay:SetterValueBindingHelper
Property="Foreground"
Binding="{Binding Brush2.BrushColor}"/>
</Setter.Value>
</Setter>
(Note: I didn't actually test a working example for your code; you might have to fiddle a bit with the binding itself to get the data context right, as I'm not sure it will correctly inherit the context from the page with the extra indirection.)

Refresh template custom control after clearing and re-adding MergedDictionaries

The situation
I made a custom control MessageBar that has a style defined in a resource dictionary. This control has a dependency property Message that, like the name says, will contain a message.
public class MessageBar : Control
{
public static readonly DependencyProperty MessageProperty =
DependencyProperty.Register("Message", typeof(string), typeof(MessageBar),
new FrameworkPropertyMetadata(string.Empty, OnMessageChanged));
static MessageBar()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MessageBar), new FrameworkPropertyMetadata(typeof(MessageBar)));
}
public string Message
{
get { return (string)GetValue(MessageProperty); }
set { SetValue(MessageProperty, value); }
}
private static void OnMessageChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
MessageBar messageBar = (MessageBar)d;
if (e.NewValue != null && !string.IsNullOrWhiteSpace(e.NewValue.ToString()))
{
messageBar.Visibility = Visibility.Visible;
if (messageBar.textBlock == null)
messageBar.textBlock = messageBar.GetChildOfType<TextBlock>();
// Lots of unnecessary code
}
}
Style
<Style TargetType="{x:Type controls:MessageBar}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type controls:MessageBar}">
<Border Background="LightYellow"
BorderBrush="Black"
BorderThickness="1,0,1,1"
CornerRadius="0,0,10,10">
<StackPanel VerticalAlignment="Center"
Orientation="Horizontal"
Margin="10,0,10,0">
<!-- Actual text -->
<TextBlock Padding="4,2,4,2"
Margin="5,0,0,0"
x:Name="tbText"
Text="{TemplateBinding Message}"
FontSize="16"
FontWeight="ExtraBold" />
</StackPanel>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
When the application starts, the default style is loaded in the merged dictionaries. After the user logs in, the style that the user chose (this can be different of the default style) is loaded in the merged dictionaries. Reloading the style happens by clearing the merged dictionaries and re-adding the correct resource dictionaries to the merged dictionaries.
Application.Current.Resources.MergedDictionaries.Clear();
Application.Current.Resources.MergedDictionaries.Add(new ResourceDictionary()
{
Source = ...
});
// Adding happens a few times.
The new style is correctly loaded and this is also visible in the UI. The UI changes correctly.
The problem
The problem happens when after clearing and re-adding the merged dictionaries, I try to find the child of type TextBlock in the OnMessageChanged method.
messageBar.textBlock = messageBar.GetChildOfType<TextBlock>();
I am 100% sure that there is nothing wrong with my GetChildOfType<>() method. It works correctly when used somewhere else.
When I execute this after re-adding, there are no child elements of MessageBar. ChildrenCount is 0.
When the merged dictionaries aren't cleared and re-added, there is a child element of type TextBlock found. This is what I want.
My guess is that after clearing and re-adding, the MessageBar doesn't have a correct reference to the style. And because of that, the template isn't applied.
What I already tried
I already tried overriding the ApplyTemplate() and OnStyleChanged() methods of the MessageBar control. But nothing works.
Question
How can I reload the style so that I (the GetChildOfType<TextBlock>() method) can find the TextBlock to set my message in the OnMessageChanged method.
Thanks in advance!
Greetings Loetn.
Try messageBar.ApplyTemplate() before calling GetChildOfType

Allowing named elements in a multi-content custom control

Requirement
Let's start with what I am trying to achieve. I want to have a grid with 2 columns and a grid splitter (there is a little more to it that that, but let's keep it simple). I want to be able to use this grid in a lot of different places, so instead of creating it each time I want to make a custom control that contain two ContentPresenters.
The end goal is effectively to be able to write XAML like this:
<MyControls:MyGrid>
<MyControls:MyGrid.Left>
<Label x:Name="MyLabel">Something unimportant</Label>
</MyControls:MyGrid.Left>
<MyControls:MyGrid.Right>
<Label>Whatever</Label>
</MyControls:MyGrid.Right>
</MyControls:MyGrid>
IMPORTANT: Notice that I want to apply a Name to my Label element.
Attempt 1
I did a lot of searching for solutions, and the best way I found was to create a UserControl along with a XAML file that defined my grid. This XAML file contained the 2 ContentPresenter elements, and with the magic of binding I was able to get something working which was great. However, the problem with that approach is not being able to Name the nested controls, which results in the following build error:
Cannot set Name attribute value 'MyName' on element 'MyGrid'. 'MyGrid'
is under the scope of element 'MyControls', which already had a name
registered when it was defined in another scope.
With that error in hand, I went back to Dr. Google...
Attempt 2 (current)
After a lot more searching I found some information here on SO that suggested the problem was due to having an associated XAML file with the MyGrid class, and the problem should be solvable by removing the XAML and creating all the controls via code in the OnInitialized method.
So I headed off down that path and got it all coded and compiling. The good news is that I can now add a Name to my nested Label control, the bad news is nothing renders! Not in design mode, and not when running the application. No errors are thrown either.
So, my question is: What am I missing? What am I doing wrong?
I am also open to suggestions for other ways to meet my requirements.
Current code
public class MyGrid : UserControl
{
public static readonly DependencyProperty LeftProperty = DependencyProperty.Register("Left", typeof(object), typeof(MyGrid), new PropertyMetadata(null));
public object Left
{
get { return (object)GetValue(LeftProperty); }
set { SetValue(LeftProperty, value); }
}
public static readonly DependencyProperty RightProperty = DependencyProperty.Register("Right", typeof(object), typeof(MyGrid), new PropertyMetadata(null));
public object Right
{
get { return (object)GetValue(RightProperty); }
set { SetValue(RightProperty, value); }
}
Grid MainGrid;
static MyGrid()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MyGrid), new FrameworkPropertyMetadata(typeof(MyGrid)));
}
protected override void OnInitialized(EventArgs e)
{
base.OnInitialized(e);
//Create control elements
MainGrid = new Grid();
//add column definitions
ColumnDefinition leftColumn = new ColumnDefinition()
{
Name = "LeftColumn",
Width = new GridLength(300)
};
MainGrid.ColumnDefinitions.Add(leftColumn);
MainGrid.ColumnDefinitions.Add(new ColumnDefinition()
{
Width = GridLength.Auto
});
//add grids and splitter
Grid leftGrid = new Grid();
Grid.SetColumn(leftGrid, 0);
MainGrid.Children.Add(leftGrid);
GridSplitter splitter = new GridSplitter()
{
Name = "Splitter",
Width = 5,
BorderBrush = new SolidColorBrush(Color.FromArgb(255, 170, 170, 170)),
BorderThickness = new Thickness(1, 0, 1, 0)
};
MainGrid.Children.Add(splitter);
Grid rightGrid = new Grid();
Grid.SetColumn(rightGrid, 1);
MainGrid.Children.Add(rightGrid);
//add content presenters
ContentPresenter leftContent = new ContentPresenter();
leftContent.SetBinding(ContentPresenter.ContentProperty, new Binding("Left") { Source = this });
leftGrid.Children.Add(leftContent);
ContentPresenter rightContent = new ContentPresenter();
rightContent.SetBinding(ContentPresenter.ContentProperty, new Binding("Right") { Source = this });
rightGrid.Children.Add(rightContent);
//Set this content of this user control
this.Content = MainGrid;
}
}
After some discussion via comments, it quickly became clear that neither of my attempted solutions was the correct way to go about it. So I set out on a third adventure hoping this one would be the final solution... and it seems it is!
Disclaimer: I do not yet have enough experience with WPF to confidently say that my solution is the best and/or recommended way to do this, only that it definitely works.
First of all create a new custom control: "Add" > "New Item" > "Custom Control (WPF)". This will create a new class that inherits from Control.
In here we put our dependency properties for bind to out content presenters:
public class MyGrid : Control
{
public static readonly DependencyProperty LeftProperty = DependencyProperty.Register("Left", typeof(object), typeof(MyGrid), new PropertyMetadata(null));
public object Left
{
get { return (object)GetValue(LeftProperty); }
set { SetValue(LeftProperty, value); }
}
public static readonly DependencyProperty RightProperty = DependencyProperty.Register("Right", typeof(object), typeof(MyGrid), new PropertyMetadata(null));
public object Right
{
get { return (object)GetValue(RightProperty); }
set { SetValue(RightProperty, value); }
}
static MyGrid()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MyGrid), new FrameworkPropertyMetadata(typeof(MyGrid)));
}
}
When you add this class file in Visual Studio, it will automatically create a new "Generic.xaml" file in the project containing a Style for this control, along with a Control Template within that style - this is where we define our control elements...
<Style TargetType="{x:Type MyControls:MyGrid}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type MyControls:MyGrid}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="500" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid Grid.Column="0">
<ContentPresenter x:Name="LeftContent" />
</Grid>
<GridSplitter Width="5" BorderBrush="#FFAAAAAA" BorderThickness="1,0,1,0">
</GridSplitter>
<Grid Grid.Column="1">
<ContentPresenter x:Name="RightContent" />
</Grid>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
The final step is to hook up the bindings for the 2 content presenters, so back to the class file.
Add the following override method to the MyGrid class:
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
//Apply bindings and events
ContentPresenter leftContent = GetTemplateChild("LeftContent") as ContentPresenter;
leftContent.SetBinding(ContentPresenter.ContentProperty, new Binding("Left") { Source = this });
ContentPresenter rightContent = GetTemplateChild("RightContent") as ContentPresenter;
rightContent.SetBinding(ContentPresenter.ContentProperty, new Binding("Right") { Source = this });
}
And that's it! The control can now be used in other XAML code like so:
<MyControls:MyGrid>
<MyControls:MyGrid.Left>
<Label x:Name="MyLabel">Something unimportant</Label>
</MyControls:MyGrid.Left>
<MyControls:MyGrid.Right>
<Label>Whatever</Label>
</MyControls:MyGrid.Right>
</MyControls:MyGrid>
Thanks to #NovitchiS for your input, your suggestions were vital in getting this approach to work

Treeview with multi colour node text

I have a project that requires the use of a treeview control. The control must have the ability for the text on each node to be formatted so the text can be multi coloured. This is best shown by the treeview used in outlook - see pic )
I historically have a windows forms control that I created to do this, my question is how easy is this to do in WPF without having to use 3rd party controls?
I historically have a windows forms control that I created to do this
Forget winforms, it's a dinosaur technology that has not been improved since 2007, it is not intended to create Rich UIs (only poor ones), and that does not support anything and forces you to write too much code and achieve less. It does not support any kind of customization and is slow as hell.
All the horrible hacks required in winforms to do anything (such as "owner draw" and "P/Invoke", whatever that means) are completely irrelevant and unneeded in WPF.
I dont really want to invest a lot of time moving of winforms to wpf if what I want to do is either not possible or too difficult
People are doing things like this in WPF, which are completely impossible in winforms, so what you're talking about here is really a "piece of cake" for WPF.
First of all, if you're getting into WPF, you must forget the traditional too-much-code-for-anything winforms approach and understand and embrace The WPF Mentality.
Here is how you implement that in WPF:
XAML:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<TreeView ItemsSource="{Binding}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<DockPanel>
<!-- this Image Control will display the "icon" for each Tree item -->
<Image Width="18" Height="16" Source="{Binding ImageSource}"
DockPanel.Dock="Left" Margin="2"/>
<!-- this TextBlock will show the main "Caption" for each Tree item -->
<TextBlock Text="{Binding DisplayName}" FontWeight="Bold"
VerticalAlignment="Center"
x:Name="DisplayName" Margin="2"/>
<!-- this TextBlock will show the Item count -->
<TextBlock Text="{Binding ItemCount, StringFormat='({0})'}"
VerticalAlignment="Center" Margin="2" x:Name="ItemCount">
<TextBlock.Foreground>
<SolidColorBrush Color="{Binding ItemCountColor}"/>
</TextBlock.Foreground>
</TextBlock>
</DockPanel>
<HierarchicalDataTemplate.Triggers>
<!-- This DataTrigger will hide the ItemCount text
and remove the Bold font weight from the DisplayName text
when ItemCount is zero -->
<DataTrigger Binding="{Binding ItemCount}" Value="0">
<Setter TargetName="ItemCount" Property="Visibility" Value="Collapsed"/>
<Setter TargetName="DisplayName" Property="FontWeight" Value="Normal"/>
</DataTrigger>
</HierarchicalDataTemplate.Triggers>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</Window>
Code Behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = DataSource.GetFolders();
}
}
Data Item:
public class Folder
{
public Folder(string displayName)
{
ImageSource = DataSource.Folder1;
Children = new List<Folder>();
ItemCountColor = "Blue";
DisplayName = displayName;
}
public Folder(string displayName, int itemCount): this(displayName)
{
ItemCount = itemCount;
}
public string DisplayName { get; set; }
public int ItemCount { get; set; }
public List<Folder> Children { get; set; }
public string ItemCountColor { get; set; }
public string ImageSource { get; set; }
}
DataSource class (A lot of boilerplate code that generates the tree entries and is not really part of the WPF side of things):
public static class DataSource
{
public const string Folder1 = "/folder1.png";
public const string Folder2 = "/folder2.png";
public const string Folder3 = "/folder3.png";
public const string Folder4 = "/folder4.png";
public static List<Folder> GetFolders()
{
return new List<Folder>
{
new Folder("Conversation History"),
new Folder("Deleted Items",102)
{
ImageSource = Folder2,
Children =
{
new Folder("Deleted Items #1"),
}
},
new Folder("Drafts",7)
{
ImageSource = Folder3,
ItemCountColor = "Green",
},
new Folder("Inbox",7)
{
ImageSource = Folder4,
Children =
{
new Folder("_file")
{
Children =
{
new Folder("__plans"),
new Folder("_CEN&ISO", 5),
new Folder("_DDMS", 1)
{
Children =
{
new Folder("Care Data Dictionary"),
new Folder("HPEN"),
new Folder("PR: Data Architecture"),
new Folder("PR: Hospital DS", 2),
new Folder("RDF"),
new Folder("Schemas"),
new Folder("Subsets"),
}
},
new Folder("_Interop")
{
Children =
{
new Folder("CDSA", 1),
new Folder("CPIS", 2),
new Folder("DMIC"),
new Folder("EOL"),
new Folder("... And so on..."),
}
}
}
}
}
}
};
}
}
Result:
As you can see, this full working sample consists of 30 lines of XAML, 1 line of C# code behind, and a simple POCO class that represents the folder structure, which consists of string, bool, int and List<T> properties and does not have any dependencies on the UI framework at all, plus the DataSource boilerplate, that does not have anything to do with WPF anyways.
Notice how my C# code is clean and simple and beautiful and does not have any horrible "owner draw" stuff or anything like that.
Also notice how the Data/Logic are completely decoupled from the UI, which gives you a HUGE amount of flexibility, scalability and maintainability. You could completely rewrite the UI into a totally different thing without changing a single line of C# code.
There is 1 line of Code behind, which sets the DataContext to a List<Folder>, the rest is achieved via DataBinding into the HierarchicalDataTemplate that defines the Visual structure of the Tree items.
This is the WPF way, to use DataBinding to simplify your life instead of a bunch of useless boilerplate piping to pass data between the UI and the Data Model.
Keep in mind that you can put literally anything inside the DataTemplate, not just text, not just read-only content. You can even put editable controls or even Video inside each tree item. the WPF Content Model does not suffer from the huge limitations imposed by other technologies.
WPF Rocks - just copy and paste my code in a File -> New Project -> WPF Application and see the results for yourself, you will need to add the png files and set their Build Action to Resource in your project:
Once you know WPF, XAML and MVVM, you will NEVER want to go back to winforms again, and you'll realize how much valuable time you've lost all these years using dead technology.

How to increase font size in XAML?

how can I increase font of a, let's say, TextBlock? I don't want to have something like this:
<TextBlock FontSize="20">
text
</TextBlock>
because it won't work correctly when user changes Windows' settings of the controls' font size. Do we have something like +VALUE (eg. +2), similar to HTML?
EDIT
That's what I meant talking about the Windows' settings:
but the answers I received totally satisfies me.
WPF doesn't have the em font size, there alternatives in the answers to this SO
The simplist may be
<ScaleTransform ScaleX="1.2" ScaleY="1.2" />
Adapting Bob Vale's answer to your original code
<TextBlock>
<TextBlock.RenderTransform>
<ScaleTransform ScaleX="1.2" ScaleY="1.2" />
</TextBlock.RenderTransform>
text
</TextBlock>
First of all you should create an application scoped style for you font sizes, as described in this answer : WPF global font size
Then, you can bind the fontsize values to a property of a static class taking the size defined in control panel, and adapt it accordingly.
For those poor souls who find this questions in need for a relative font size mechanism for design purposes such as you'd use in css, I found a hack that appears to work in WPF.
It's used this way:
<StackPanel>
<TextBlock>outer</TextBlock>
<ContentControl local:RelativeFontSize.RelativeFontSize="2">
<StackPanel>
<TextBlock>inner</TextBlock>
<ContentControl local:RelativeFontSize.RelativeFontSize="2">
<StackPanel>
<TextBlock>innerest</TextBlock>
</StackPanel>
</ContentControl>
</StackPanel>
</ContentControl>
</StackPanel>
Which gives this:
And here's the code:
public static class RelativeFontSize
{
public static readonly DependencyProperty RelativeFontSizeProperty =
DependencyProperty.RegisterAttached("RelativeFontSize", typeof(Double), typeof(RelativeFontSize), new PropertyMetadata(1.0, HandlePropertyChanged));
public static Double GetRelativeFontSize(Control target)
=> (Double)target.GetValue(RelativeFontSizeProperty);
public static void SetRelativeFontSize(Control target, Double value)
=> target.SetValue(RelativeFontSizeProperty, value);
static Boolean isInTrickery = false;
public static void HandlePropertyChanged(Object target, DependencyPropertyChangedEventArgs args)
{
if (isInTrickery) return;
if (target is Control control)
{
isInTrickery = true;
try
{
control.SetValue(Control.FontSizeProperty, DependencyProperty.UnsetValue);
var unchangedFontSize = control.FontSize;
var value = (Double)args.NewValue;
control.FontSize = unchangedFontSize * value;
control.SetValue(Control.FontSizeProperty, unchangedFontSize * value);
}
finally
{
isInTrickery = false;
}
}
}
}

Categories