Strange behaviour of overridden Style of TextBlock - c#

Some days ago I've faced with strange behaviour of text inside Button (I guess the same behaviour I would got for other ContentControls). Let me explain the situation. I have a style definition in App.xaml for TextBlock:
<Application.Resources>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Margin" Value="10"/>
</Style>
</Application.Resources>
In MainWindow.xaml I have the same style definition, that should override style that defined in App.xaml. Also I have 3 buttons in Window. In first button explicitly defined TextBlock control inside button's content. For second button I set a string as content in codebehind. For third button I set an integer value as content in codebehind. Here is code of MainWindow.xaml:
<StackPanel>
<StackPanel.Resources>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Margin" Value="0"/>
</Style>
</StackPanel.Resources>
<Button Name="Button1">
<Button.Content>
<TextBlock Text="Button with text block"/>
</Button.Content>
</Button>
<Button Name="Button2" />
<Button Name="Button3" />
</StackPanel>
and MainWindow.xaml.cs:
private void Window_Loaded(object sender, RoutedEventArgs e)
{
Button2.Content = "Button with string";
Button3.Content = 16;
}
And now what we see? Text in first and third buttons, as expected, have margins 0px, but text in second button have margins 10px! The question is: why second button has 10px margins and how set zero margins for second button (removing style from App.xaml is not possible)?
Thank you!

When I change
Button2.Content = "Button with string";
to
Button2.Content = "Button with _string";
the button's margin changes from 10 to 0.
This is a bug in WPF; it already has been reported on Microsoft Connect.
I am not 100% sure but I think the behavior you saw is caused by the same root cause.
By the way: the correct behavior would be that buttons 2 and 3 have Margin=10; this is because resource lookup is performed along the logical tree, not along the visual tree. The TextBlocks in the buttons 2 and 3 are not inside the logical tree of the StackPanel.

I can't give you a definitive answer, but I notice that it's the difference between setting a string and an integer which causes the different styles to be applied.
Since setting Content to a value that requires a conversion results in correct style being applied, I tried this:
private void WindowLoaded(object sender, RoutedEventArgs e)
{
Button2.Content = new TextHolder("Button with string");
Button3.Content = 16;
}
public class TextHolder
{
private readonly string _text;
public TextHolder(string text)
{
_text = text;
}
public override string ToString()
{
return _text;
}
}
and the margin is now 0. I would be interested in understanding exactly what is going on.

Related

Easier way in .net Avalonia to change the background color of the Window's System Top bar?

In dotnet's Avalonia-UI framework.
I'm using a dark UI and I managed to make everything dark as per this example but one thing: the window's System top bar in Windows OS.
I have seen in this issue in github that I can set the property HasSystemDecorations="false" to make it go away, but then I would have to implement myself the top bar with the drag functionality, title, close, maximize, minimize, etc, which is a pain when all I want is to change the background color.
What would be the easier way to make the window top bar change to a dark background color?
If the only way is using HasSystemDecorations then what would be the minimal example to implement the dark top bar with the common funcionality to close/minimize/maximize/drag?
Yes, you have to set HasSystemDecorations="false" and implement your own title bar. I have a basic template on Github for how to do this using version 0.10 and fluent theme.
It is actually quite easy, because Avalonia provides a lot of convenience methods for achieving that.
Overview:
Set
ExtendClientAreaToDecorationsHint="True"
ExtendClientAreaChromeHints="NoChrome"
ExtendClientAreaTitleBarHeightHint="-1"
for the Window and then implement a titlebar. For example the close button could look something like this:
<Button Width="46"
VerticalAlignment="Stretch"
BorderThickness="0"
Name="CloseButton"
ToolTip.Tip="Close">
<Button.Resources>
<CornerRadius x:Key="ControlCornerRadius">0</CornerRadius>
</Button.Resources>
<Button.Styles>
<Style Selector="Button:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="Red"/>
</Style>
<Style Selector="Button:not(:pointerover) /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Background" Value="Transparent"/>
</Style>
<Style Selector="Button:pointerover > Path">
<Setter Property="Fill" Value="White"/>
</Style>
<Style Selector="Button:not(:pointerover) > Path">
<Setter Property="Fill" Value="{DynamicResource SystemControlForegroundBaseHighBrush}"/>
</Style>
</Button.Styles>
<Path Margin="10,0,10,0"
Stretch="Uniform"
Data="M1169 1024l879 -879l-145 -145l-879 879l-879 -879l-145 145l879 879l-879 879l145 145l879 -879l879 879l145 -145z"></Path>
</Button>
If you set IsHitTestVisible="False" on a control, the window below can be dragged in that area. So wrap your whole titlebar in for example a DockPanel:
<DockPanel Background="Black"
IsHitTestVisible="False"
Name="TitleBarBackground"></DockPanel>
Now you obviously still need to mimic the behaviour of the buttons. This can be done in principal like that (again for a concrete example check out the Github repo above):
minimizeButton = this.FindControl<Button>("MinimizeButton");
maximizeButton = this.FindControl<Button>("MaximizeButton");
maximizeIcon = this.FindControl<Path>("MaximizeIcon");
maximizeToolTip = this.FindControl<ToolTip>("MaximizeToolTip");
closeButton = this.FindControl<Button>("CloseButton");
windowIcon = this.FindControl<Image>("WindowIcon");
minimizeButton.Click += MinimizeWindow;
maximizeButton.Click += MaximizeWindow;
closeButton.Click += CloseWindow;
windowIcon.DoubleTapped += CloseWindow;
private void CloseWindow(object sender, Avalonia.Interactivity.RoutedEventArgs e)
{
Window hostWindow = (Window)this.VisualRoot;
hostWindow.Close();
}
private void MaximizeWindow(object sender, Avalonia.Interactivity.RoutedEventArgs e)
{
Window hostWindow = (Window)this.VisualRoot;
if (hostWindow.WindowState == WindowState.Normal)
{
hostWindow.WindowState = WindowState.Maximized;
}
else
{
hostWindow.WindowState = WindowState.Normal;
}
}
private void MinimizeWindow(object sender, Avalonia.Interactivity.RoutedEventArgs e)
{
Window hostWindow = (Window)this.VisualRoot;
hostWindow.WindowState = WindowState.Minimized;
}
Now the last step is that you need to change the icon of the maximize button depending on the window state. For example if you drag a maximized window, it will automatically become restored down and the icon of the maximize button needs to change. Therefore you need to subscribe to the window state of your host window, which can be done by overriding the Window.HandleWindowStateChanged method or by doing something like this:
private async void SubscribeToWindowState()
{
Window hostWindow = (Window)this.VisualRoot;
while (hostWindow == null)
{
hostWindow = (Window)this.VisualRoot;
await Task.Delay(50);
}
hostWindow.GetObservable(Window.WindowStateProperty).Subscribe(s =>
{
hostWindow.Padding = hostWindow.OffScreenMargin;
if (s != WindowState.Maximized)
{
maximizeIcon.Data = Avalonia.Media.Geometry.Parse("M2048 2048v-2048h-2048v2048h2048zM1843 1843h-1638v-1638h1638v1638z");
maximizeToolTip.Content = "Maximize";
}
if (s == WindowState.Maximized)
{
maximizeIcon.Data = Avalonia.Media.Geometry.Parse("M2048 1638h-410v410h-1638v-1638h410v-410h1638v1638zm-614-1024h-1229v1229h1229v-1229zm409-409h-1229v205h1024v1024h205v-1229z");
maximizeToolTip.Content = "Restore Down";
}
});
}
Actually in the snippet above there is one more detail, which needs some attention. At least on windows, a maximized window is actually bigger than the screen. If you dont want your content to go out of the screens' bounds, you need to add a margin to your main control inside the window. Therefore the Padding of the hostWindow is changed accordingly.
Avalonia provides an IWindowImpl.OffScreenMargin property that describes the margin around the window that is offscreen.
You can directly bind to this property in the window's .axml
<Window xmlns="https://github.com/avaloniaui"
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"
ExtendClientAreaToDecorationsHint="True"
ExtendClientAreaChromeHints="NoChrome"
ExtendClientAreaTitleBarHeightHint="-1"
Padding="{Binding $self.OffScreenMargin}">
There is a way without having to create your own minimize/maximize/close buttons (I only tested it on Windows).
In your MainWindow.axaml:
<Window xmlns="https://github.com/avaloniaui"
...
TransparencyLevelHint="AcrylicBlur"
Background="Transparent"
ExtendClientAreaToDecorationsHint="True"/>
<Grid RowDefinitions="30,*">
<!-- Title bar -->
<Grid ColumnDefinitions="Auto,*" IsHitTestVisible="False" Background="Black">
<Image Grid.Column="0" VerticalAlignment="Center" Source="/Assets/YOUR-PATH-TO-YOUR-APP-ICON-IMAGE" Width="18" Margin="12,0,12,0" ></Image>
<TextBlock Grid.Column="1" VerticalAlignment="Center" FontSize="12" >YOUR-APPLICATION-TITLE-HERE</TextBlock>
</Grid>
<!-- Window content -->
<Your-User-Content-Here Grid.Row="1" Background="#222222" />
</Grid>
Here is the example in the AvaloniaUI documentation.
Here is an example in a real project with a black system bar.

MouseLeftButtonUp does not fire for ScrollViewer in WPF control template

In WPF and C# I am trying to set up a mouse drag scrolling feature for a ScrollViewer contained within a control template for a document viewer. The problem: I have not been able to get the MouseLeftButtonEvent to fire.
It is basically the default DocumentViewer template, with a few features modified. Here is an outline in XAML:
<Style x:Key="DocumentViewerStyle1" BasedOn="{x:Null}" TargetType="{x:Type DocumentViewer}">
<!--...—>
<Setter Property="ContextMenu" Value="{x:Null}" /> <!--So does not mess up right click, if I use that-->
<!--...-->
<ControlTemplate TargetType="{x:Type DocumentViewer}">
<!--...-->
<ScrollViewer x:Name="PART_ContentHost" CanContentScroll="True"
IsHitTestVisible="True" HorizontalScrollBarVisibility="Auto"
Grid.Row="1" Loaded ="OnScrollViewerLoaded" />
<DockPanel Grid.Row="1" >
<!-...-->
</ControlTemplate>
</Style>
I use the following in code behind so that I can access the ScrollViewer. If one changes “Left” to “Right” in the method below, it works to perfection, but of course with the right mouse button rather than the left.
public partial class PrintPreview : Window
{
private ScrollViewer nomad;
etc. and
private void OnScrollViewerLoaded(object sender, RoutedEventArgs e)
{
nomad = (ScrollViewer)sender;
nomad.AddHandler(MouseLeftButtonDownEvent, new MouseButtonEventHandler(OnMouseButtonDown), true);
nomad.AddHandler(MouseLeftButtonUpEvent, new MouseButtonEventHandler(OnMouseButtonUp), true);
nomad.AddHandler(MouseMoveEvent, new MouseEventHandler(OnMouseMove), true);
}
The OnMouseButtonUp event handler, for example, is
private void OnMouseButtonUp(object sender, MouseButtonEventArgs e)
{
nomad.Cursor = Cursors.IBeam;
nomad.ReleaseMouseCapture();
}
Have tried various things found here: No help from using Preview events for my three mouse events. No help from setting Focusable="False" for the ScrollViewer, or for setting a Background for it. Any suggestions? Thanks!

Why does RelativeSource Binding behave inconsistent?

Note: I know I should use {Binding RelativeSource={RelativeSource TemplatedParent}} to resolve the presented issue but I want to know why the issue appears.
I have a custom control CustomTextControl with a ControlTemplate where I use RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type local:CustomTextControl}} and when I display it inside a Window, everything is displayed as expected.
But when I try to print the same control with the PrintVisual method of System.Windows.Controls.PrintDialog, the binding is not evaluated correctly.
Example:
public class CustomTextControl : ContentControl
{
static CustomTextControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomTextControl), new FrameworkPropertyMetadata(typeof(CustomTextControl)));
}
}
Generic.xaml (local refers to the xmlns:local definition of my project namespace):
<Style TargetType="{x:Type local:CustomTextControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:CustomTextControl}">
<TextBlock
Background="Red"
Text="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type local:CustomTextControl}},Path=Content}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
MainWindow content:
<Grid>
<local:CustomTextControl Content="My text to be displayed" VerticalAlignment="Top" HorizontalAlignment="Left" Margin="10"/>
<Button Content="PrintTest" Click="Button_Click" VerticalAlignment="Top" HorizontalAlignment="Left" Margin="10,100,10,10"/>
</Grid>
The control is displayed with red background and the specified text content.
MainWindow code behind:
private void Button_Click(object sender, RoutedEventArgs args)
{
var e = new CustomTextControl();
e.Content = "My text to be printed";
e.Margin = new Thickness(30);
e.UpdateLayout();
var print = new PrintDialog();
if (print.ShowDialog() != true) return;
var w = print.PrintTicket.PageMediaSize.Width ?? 600;
var h = print.PrintTicket.PageMediaSize.Height ?? 1000;
e.Measure(new Size(w, h));
e.Arrange(new Rect(0, 0, w, h));
print.PrintVisual(e, "Test Printing");
}
As you see, I create a separate control that's sufficiently similar to the control inside the mainwindow.
Result: the printed document contains the red background but not the text content.
My question: why is the text content displayed in the window but not in the printed document?
Update
If I place a MessageBox.Show("Test"); right before the print.PrintVisual(e, "Test Printing");, then the printed document has red background, so it is some sort of work/timing issue.
I was able to resolve my specific example by using Dispatcher.BeginInvoke on the printing statement but for a little modified example involving a Data-Bound CheckBox, this was not enough, even with DispatcherPriority.ApplicationIdle.
I think this happens, because your control isn't in visual tree. RelativeSource binding searches in parent of current control.
You can try to replace your RelativeSource with TemplateBinding inside Template definition.
Try to Use
Text="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type local:CustomTextControl}},Path=Content}"/>
instead of:
Text="{TemplateBinding Content}"/>

Vertically Centering MenuItem text in a Fluent Ribbon with C# WPF

I'm working with Fluent.Ribbon, and I am desperately trying to vertically center the text of second level menu items. I actually wouldn't mind making second level have the same style as first level items if that would be easier.
From digging through the source code, I think I've found that the separate style is defined by the ControlTemplate ApplicationMenuSecondLevelItemTemplate. However, since I'm working on learning WPF, I'm not sure how to override that with styles.
I've tried simpler solutions, like this one which just makes the text disappear.
I've also tried all of the option in this post. However, none of those work either, and when I fix the last one to be the following to get rid of errors, the application just crashes.
<Style x:Key="CenteredTextMenuItem" TargetType="{x:Type MenuItem}">
<Setter Property="HeaderTemplate">
<Setter.Value>
<DataTemplate>
<TextBox Text="{Binding}" HorizontalAlignment="Stretch"
HorizontalContentAlignment="Center" FontSize="16" FontWeight="Bold"/>
</DataTemplate>
</Setter.Value>
</Setter>
<Setter Property="Height" Value="30"/>
<Setter Property="Width" Value="188"/>
</Style>
How can I center a second level MenuItem's text with the Fluent.Ribbon control?
Thanks.
Edit:
Here is an image showing how the second level is not centered.
And nothing complicated with the code:
<Fluent:MenuItem Header="Print Invoice" Icon="NavIcons\Print_32.png" />
Here are possible solutions for your problem.
Simple, but somewhat ugly.
Create an event handler in code behind for the Loaded event of your menu items.
<Fluent:MenuItem Loaded="MenuItem_Loaded"/>
In this event handler, you can find the control template parts manually and override their appearance:
private void MenuItem_Loaded(object sender, RoutedEventArgs e)
{
Fluent.MenuItem menuItem = sender as Fluent.MenuItem;
if (menuItem != null)
{
TextBlock textBlock = menuItem.Template.FindName("textBlockDesc", menuItem) as TextBlock;
if (textBlock != null)
{
textBlock.Visibility = System.Windows.Visibility.Collapsed;
}
textBlock = menuItem.Template.FindName("textBlock", menuItem) as TextBlock;
if (textBlock != null)
{
textBlock.VerticalAlignment = System.Windows.VerticalAlignment.Center;
}
}
}
This is a bad solution, don't do it like that. I've just shown it so you can get an idea how could you access the template parts if you wish to. These strings "textBlockDesc" and "textBlock" are the control template parts defined in the Fluent theme.
Create your own style and control template.
Since you can't inherit a control template, you have to copy it from the Fluent theme and put it in your resources. The disadvantage is that you'll have to manually resync that template with the original one if there will be an update.
I don't put an example here, because that solution isn't really good too.
Override the menu item style manually.
Set the style of your menu items manually to the "first level" menu items' style.
<Fluent:MenuItem Style="{DynamicResource ApplicationMenuStyle}"/>

Setting button flat style programmatically

I want to give a button a flat style programmatically when certain conditions occur.
This question shows how I can set a style to a control programmatically, having already defined it in XAML.
This question shows that a flat button style already exists, so it is not necessary to create one in XAML.
ToolBar.ButtonStyleKey returns a ResourceKey, and the corresponding style is not defined in my window (it's in ToolBar). How do I use it in code to set the style programmatically?
As an alternative, you can try this:
XAML
<Button Name="FlatButton" Width="100" Height="30" Content="Test" />
Code behind
private void Button_Click(object sender, RoutedEventArgs e)
{
FlatButton.Style = (Style)FindResource(ToolBar.ButtonStyleKey);
}
This is a workaround that works. Add a style based on ToolBar.ButtonStyleKey to Window.Resources as follows:
<Window.Resources>
<Style x:Key="MyStyle" BasedOn="{StaticResource {x:Static ToolBar.ButtonStyleKey}}" TargetType="Button" />
</Window.Resources>
Then, in code, refer to it as per first link in this question:
button.Style = this.Resources["MyStyle"] as Style;
I'd prefer to have a code-only solution (no XAML) for this, but this works just as well.

Categories

Resources