Style Setter Freezing Brush - wpf

I seem to have run into some behavior regarding WPF ResourceDictionaries, Brushes, and Styles (at least that's what I've noticed so far) that is counter to my understanding of how these things should work. Basically, if I reference a Brush from a Setter withing a Style in a ResourceDictionary it causes the Brush to become frozen. The example below illustrates this, as I get an InvalidOperationException when I try to change the Color on the shared Brush within my button's Click event handler. It should cause both Rectangle's color to change, as they both use the same shared Brush, but I get the exception instead.
<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="350" Width="525">
<Window.Resources>
<SolidColorBrush x:Key="TestBrush" Color="Red" />
<Style TargetType="Rectangle">
<Setter Property="Fill" Value="{StaticResource TestBrush}" />
</Style>
</Window.Resources>
<StackPanel>
<Button Name="Button1" Content="Change Color" Click="Button1_Click" />
<Rectangle Height="20" />
<Rectangle Height="20" />
</StackPanel>
</Window>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button1_Click(object sender, RoutedEventArgs e)
{
var brush = (SolidColorBrush)FindResource("TestBrush");
// InvalidOperationException Here. Brush is Frozen/Read-Only
brush.Color = Colors.Blue;
}
}
If I simply remove the Style (more specifically the Setter) and reference the Brush (still from the ResourceDictionary) directly from each Rectangle, I get the expected behavior of the Rectangles' colors changing in tandem from the button click event. See code below (button click event hanlder remains the same).
<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="350" Width="525">
<Window.Resources>
<SolidColorBrush x:Key="TestBrush" Color="Red" />
</Window.Resources>
<StackPanel>
<Button Name="Button1" Content="Change Color" Click="Button1_Click" />
<Rectangle Height="20" Fill="{StaticResource TestBrush}" />
<Rectangle Height="20" Fill="{StaticResource TestBrush}" />
</StackPanel>
</Window>
I only see the Brush becoming frozen when it is referenced as a StaticResource from a Style's Setter. I can actaully reference the same Brush from other locations within the ResourceDictionary without it becoming frozen; i.e. the contents of ControlTemplates.
Can anyone please explain what is going on with this strange behavior and if it's by-design or a bug?
Thanks,
Brandon

...once a style has been applied, it is sealed and cannot be changed.
If you want to dynamically change a style that has already been
applied, you must create a new style to replace the existing one. For
more information, see the IsSealed property.
See http://msdn.microsoft.com/en-us/library/ms745683.aspx

Related

WPF Focus, can't get both logical and keyboard focus

I have been through several other postings about WPF and focus, and the best I can figure there's something I'm missing in my code. I'm working on an app using strict MVVM so I'm trying to avoid any code-behind in the view files (using attached behaviors when code-behind is necessary), but at this point even putting the focus code in the code-behind of the view it's not working.
I have an application with a main window and I'm trying to make a search window popup on a hot key. I'd like as soon as the user hits the hot key, the keyboard focus to be on the search text so it's just hotkey and then type your search term. Everything but the logical focus is working at this point, even though keyboard claims to have focus on the element.
I can't seem to get it to take both keyboard and logical focus at the same time from the code. However, if I hit Tab as soon as the search box appears, I'm put right into the text box.
Main window code:
<ribbon:RibbonWindow x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ribbon="clr-namespace:Microsoft.Windows.Controls.Ribbon;assembly=RibbonControlsLibrary"
xmlns:attached="clr-namespace:UserInterface.Attached"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:command="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Extras.WPF45"
xmlns:viewModels="clr-namespace:UserInterface.ViewModels"
xmlns:views="clr-namespace:UserInterface.Views"
xmlns:layout="clr-namespace:UserInterface.ViewModels.Layout"
xmlns:layout1="clr-namespace:UserInterface.Views.Layout"
MinHeight="560"
MinWidth="950"
WindowStartupLocation="CenterScreen"
Icon="{Binding Layout.IconPath}"
DataContext="{Binding Main, Source={StaticResource Locator}}"
FocusManager.FocusedElement="{Binding ElementName=LayoutControl}"
Title="{Binding Layout.Title}">
<!-- Ribbon menu shortcuts -->
<Window.InputBindings>
<KeyBinding Modifiers="Control" Key="T" Command="{Binding Layout.Commands[GlobalObjectSearch]}" />
</Window.InputBindings>
<Grid>
<ContentPresenter Content="{Binding Layout}" x:Name="LayoutControl">
<ContentPresenter.Resources>
<DataTemplate DataType="{x:Type layout:MainViewModel}">
<layout1:MainView/>
</DataTemplate>
</ContentPresenter.Resources>
</ContentPresenter>
</Grid>
</ribbon:RibbonWindow>
Code to make search window appear:
public SelfClosingDialogView ShowSelfClosingDialog(IWindowDialogViewModel dataContext)
{
dataContext.CheckWhetherArgumentIsNull(nameof(dataContext));
var view = new SelfClosingDialogView
{
DataContext = dataContext,
Owner = Application.Current?.MainWindow
};
view.Show();
return view;
}
Search window code (Reused, so generic):
<Window x:Class="UserInterface.Views.DialogViews.SelfClosingDialogView"
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:attached="clr-namespace:UserInterface.Attached"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
mc:Ignorable="d"
SizeToContent="WidthAndHeight"
WindowStyle="None"
WindowStartupLocation="CenterOwner">
<!-- Allow view models to cause the window to close -->
<Window.Style>
<Style TargetType="{x:Type Window}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsClosed}" Value="true">
<!-- Executes close -->
<Setter Property="attached:WindowCloseBehavior.Close" Value="true" />
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Style>
<!-- Displays the passed-in view model -->
<Grid>
<ContentPresenter x:Name="DialogPresenter" Content="{Binding}" Margin="0" />
</Grid>
</Window>
Code for my search view:
<UserControl x:Class="UserInterface.Views.DialogViews.ObjectSearchView"
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:dialogViewModels="clr-namespace:UserInterface.ViewModels.DialogViewModels"
xmlns:utils="clr-namespace:WPF.Utils"
xmlns:attached="clr-namespace:UserInterface.Attached"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance dialogViewModels:ObjectSearchViewModel}"
MinWidth="250"
Focusable="True"
FocusManager.IsFocusScope="True">
<UserControl.InputBindings>
<KeyBinding Key="Enter" Command="{Binding BrowseToObjectCommand}" />
<KeyBinding Key="Escape" Command="{Binding CloseWindowCommand}" />
</UserControl.InputBindings>
<UserControl.Resources>
<Style BasedOn="{StaticResource FormTextBlockStyle}" TargetType="TextBlock" />
</UserControl.Resources>
<StackPanel>
<TextBox Name="SearchText"
Focusable="True"
Text="{utils:ValidatingLiveBinding SearchText}"
attached:NavigatingListBoxBehavior.LinkedListBox="{Binding ElementName=SearchResults}">
</TextBox>
<ScrollViewer HorizontalScrollBarVisibility="Hidden"
VerticalScrollBarVisibility="Auto"
MaxHeight="400">
<ListBox Name="SearchResults"
ItemsSource="{Binding SearchResults}"
SelectedItem="{Binding SelectedSearchItem}"
Visibility="{Binding HasSearchResults, Converter={StaticResource BooleanToVisibilityConverter}}"
attached:ItemsControlProperties.DoubleClickCommand="{Binding BrowseToObjectCommand}"
KeyboardNavigation.IsTabStop="False"
IsSynchronizedWithCurrentItem="True" />
</ScrollViewer>
</StackPanel>
</UserControl>
And finally, the code-behind hack I'm trying to attempt to get focus (plus debugging code so that I don't lose the focus switching back and forth to Visual Studio):
public partial class ObjectSearchView : UserControl
{
public ObjectSearchView()
{
InitializeComponent();
this.Loaded += this.OnLoad;
}
private void OnLoad(object sender, RoutedEventArgs e)
{
this.PrintFocusInfo();
FocusManager.SetFocusedElement(FocusManager.GetFocusScope(this), this.SearchText);
this.PrintFocusInfo();
this.SearchText.Focus();
this.PrintFocusInfo();
Keyboard.Focus(this.SearchText);
this.PrintFocusInfo();
}
[Conditional("DEBUG")]
private void PrintFocusInfo()
{
var logicalElement = FocusManager.GetFocusedElement(FocusManager.GetFocusScope(this.SearchText));
Debug.WriteLine("Current logical focus is on '{0}', of type '{1}' ({2})".FormatInvariantCulture((logicalElement as FrameworkElement)?.Name, logicalElement?.GetType().Name, logicalElement));
var focusedElement = Keyboard.FocusedElement;
Debug.WriteLine(
"Current Keyboard Focus is on '{0}', of type '{1}' ({2})".FormatInvariantCulture(
(focusedElement as FrameworkElement)?.Name,
focusedElement.GetType().Name,
focusedElement));
}
}
Output window contents:
Current logical focus is on '', of type '' ()
Current Keyboard Focus is on '', of type 'MainWindow' (UserInterface.Views.MainWindow)
Current logical focus is on '', of type '' ()
Current Keyboard Focus is on '', of type 'MainWindow' (UserInterface.Views.MainWindow)
Current logical focus is on '', of type '' ()
Current Keyboard Focus is on 'SearchText', of type 'TextBox' (System.Windows.Controls.TextBox)
Current logical focus is on '', of type '' ()
Current Keyboard Focus is on 'SearchText', of type 'TextBox' (System.Windows.Controls.TextBox)
I've tried to include everything I can think of, but I cannot get logical focus to show anything except null.
Here is the behavior I ultimately created which fixed this for me. There's still a lot I don't know about WHY this works... but if you're having a problem getting Focus to cooperate it looks like the key is catching it when IsVisible is set to true, and having the Dispatcher set the focus for you. I linked this event to the IsVisibleChanged element on the textbox (through an attached behavior).
private void SetFocusOnVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if ((bool)e.NewValue)
{
this.Dispatcher.BeginInvoke(DispatcherPriority.ContextIdle, new Action(() => this.AssociatedObject.Focus()));
}
}

How to remove white strip on top of WPF window with WindowStyle=None?

Is it possible to remove the white strip on top of WPF window with Window Style=None. XAML and Window is shown in the screenshot:
What you are seeing in white is the re-size border. You can remove that and still make the window resizable by setting ResizeMode="CanResizeWithGrip" AllowsTransparency="True"
If you dont want to resize at all then do this - ResizeMode="NoResize", again you wont see the border but you cant resize.
<Window x:Class="HandsOnSolution.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" Background="Green" WindowStyle="None" ResizeMode="CanResizeWithGrip" AllowsTransparency="True">
<Grid>
</Grid>
</Window>
Edit
Good point by #devuxer, if you are interested in dragging you can add this piece of code to the window mouse down event
<Window MouseLeftButtonDown="Window_MouseLeftButtonDown"/>
//code behind
private void Window_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
DragMove();
}
I had been hunting for a solution for a couple of days now, in simple words this link held the answer to my queries
though the code snippet that did the magic was:
<Setter Property="WindowChrome.WindowChrome">
<Setter.Value>
<WindowChrome CaptionHeight="0"
CornerRadius="2"
GlassFrameThickness="0"
NonClientFrameEdges="None"
ResizeBorderThickness="3"/>
</Setter.Value>
</Setter>
I just added the above property setter to the custom Window Style.
Hope that helped :)
A much simplified code, acting on only one property:
<WindowChrome.WindowChrome>
<WindowChrome CaptionHeight="0"/>
</WindowChrome.WindowChrome>
I added this piece of code:
<WindowChrome.WindowChrome>
<WindowChrome GlassFrameThickness="0,0,0,1" CornerRadius="0" />
</WindowChrome.WindowChrome>
inside <Window> paste here <Window/> and it helped :)

WPF MVVM disable focus on ContentControl (with the Tab key)

I have a window with a ContentControl binding:
<ContentControl Content="{Binding CurrentViewModel}" />
I also have an empty user control binds to the ContentControl:
<UserControl x:Class="UserControl1"
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"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
</Grid>
</UserControl>
When I run and press the tab keyboard I get a dotted rectangle around the content control. How can I disable this?
I tried using Focusable="False" and FocusVisualStyle="{x:Null}" without success...
Have you tried setting IsTabStop="False", for example...
<ContentControl Height="200" Width="200" IsTabStop="False">
<ContentControl.Content>
<TextBox/>
</ContentControl.Content>
</ContentControl>
and I would suggest you combine it with this trick:
Loaded += (sender, e) =>
MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
from the answer to this question: WPF and initial focus
Override the ContentControl Style and add property FocusVisualStyle as null and implement by using style property
<ContentControl Height="200" Width="200" FocusVisualStyle="{x:Null}">
<ContentControl.Content>
<local:UserControl1/>
</ContentControl.Content>
</ContentControl>

UserControl Canvas.Left binding doesnt work

So the problem is this. I need UserControl which will have set Canvas.Top and Canvas.Left but these properties are binded from the ViewModel. For simplicity let's have this code for the user control with no code behind:
<UserControl x:Class="BadBinding.MyUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Canvas.Left="{Binding ElementName=slider, Path=Value}"
>
<Grid Width="100" Background="Red">
<Slider x:Name="slider" Minimum="100" Maximum="250" />
</Grid>
</UserControl>
And this code for the main window:
<Window x:Class="BadBinding.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"
xmlns:local="clr-namespace:BadBinding"
>
<Canvas>
<local:MyUserControl />
</Canvas>
</Window>
I don't know why is binding not working. When you set Canvas.Left directly to some value everything is fine as well as writing content of the user control directly to the main window.
I think its because the UserControl is constructed befor being added to the Canvas and since Canvas.Left is an attached property it probably won't resolve correctly.
Try using a Reference binding.
<UserControl x:Class="BadBinding.MyUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Canvas.Left="{Binding Source={x:Reference Name=slider}, Path=Value}"
>
<Grid Width="100" Background="Red">
<Slider x:Name="slider" Minimum="100" Maximum="250" />
</Grid>
</UserControl>
Note: you may get a compile warning, but it will still compile.
But I think the best option would be to create a property on your usercontrol to bind the value, this will also work.
I tried a lot with Bindings but it dint worked for me too.. so if you wanna go with EventHandler then the following workaround may help you..
Remove the Bindings and add an event handler to ValueChanged event
In your MyUserControl.xaml
<UserControl x:Class="BadBinding.MyUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid Width="100" Background="Red">
<Slider x:Name="slider" Minimum="100" Maximum="250" ValueChanged="slider1_ValueChanged" />
</Grid>
</UserControl>
In your MyUserControl.xaml.cs
private void slider1_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
Canvas.SetLeft(this, slider1.Value);
}
I tried this and working for me if you find any problem then let me know..

WPF EventHandler fired on the wrong Element

I am puzzled by this:
I have made a very simple example:
MainWindow.xaml:
<Window x:Class="Test.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">
<Window.Resources>
<Style TargetType="RichTextBox">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="RichTextBox">
<Grid Height="100" Width="200">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Label Background="Blue" Grid.Row="0">Label</Label>
<Border PreviewMouseDown="Border_PreviewMouseDown" Background="Red" Grid.Row="1">
<ScrollViewer x:Name="PART_ContentHost" />
</Border>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid>
<RichTextBox>
<FlowDocument>
<FlowDocument.Blocks>
<Paragraph>
oaizeropiazuerpoaizeurpoaizeurpaozieurpaozieru
</Paragraph>
</FlowDocument.Blocks>
</FlowDocument>
</RichTextBox>
</Grid>
</Window>
MainWindow.xaml.cs:
using System.Diagnostics;
using System.Windows;
using System.Windows.Input;
namespace Test
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Border_PreviewMouseDown(object sender, MouseButtonEventArgs e)
{
Debug.WriteLine("Click !");
}
}
}
now, as I explicitly put the PreviewMouseDown EventHandler on the Border and not on the label in my template, I expect that it will fire when I click on the (red) border of the control, but not when I click on the (blue) label.
However, the event is fired when I click on the (red) Border AND when I click on the (blue) label.
so why does the Label call an EventHandler that I explicitly attached to an other part of the controlTemplate (i.e.: the border)?
I've checked: If I remove the PreviewMouseDown="Border_PreviewMouseDown" code from the border's properties, the event is not fired on the label any more.
what am I missing here?
and what would be the right way to do? How can I design my controlTemplate so that the PreviewMouseDown Event is fired only by a sub-part of the templated control?
thanks in advance
Edit: following Snowbear's answer, I checked the originalSource of the event when I click on the Label. It is indeed the border. Why is this so? in what way is the border encapsulating the label in the template above? I specifically set them on different grid rows to avoid this, so how come?
Edit2 Just for the fun, I created a handler that only prints the sender/source/originalSource of the event, and I attached it in the template to The grid, the border and the scrollviewer.
Here Is what I get when I click ONCE (and only once) on the vertical scrollbar for instance:
Clic -- Sender: System.Windows.Controls.Grid -- OriginalSource: Microsoft.Windows.Themes.ScrollChrome -- Source: MyRichTextBox
Clic -- Sender: System.Windows.Controls.Border -- OriginalSource: Microsoft.Windows.Themes.ScrollChrome -- Source: MyRichTextBox
Clic -- Sender: System.Windows.Controls.ScrollViewer -- OriginalSource: Microsoft.Windows.Themes.ScrollChrome -- Source: MyRichTextBox
Clic -- Sender: System.Windows.Controls.Grid -- OriginalSource: Microsoft.Windows.Themes.ScrollChrome -- Source: System.Windows.Controls.ScrollViewer
Clic -- Sender: System.Windows.Controls.Border -- OriginalSource: Microsoft.Windows.Themes.ScrollChrome -- Source: System.Windows.Controls.ScrollViewer
Clic -- Sender: System.Windows.Controls.ScrollViewer -- OriginalSource: Microsoft.Windows.Themes.ScrollChrome -- Source: System.Windows.Controls.ScrollViewer
this clearly settles the matter: The event is indeed tunnelled twice, for some reason, First with the TemplatedParent (i.e.: the RichtextBox) as Source, and Then with the contentPresenter (i.e.: the ScrollViewer) as Source.
By Merlin's most baggy pants, I really wonder what went through the head of the MS Dev that programmed this...
Gee, you've uncovered a very strange behavior indeed. It appears that the mouse handling for all the elements that are part of a TextBoxBase control template can be revectored to the text content element and then tunnel/bubble from there. As a result, including a label in a rich text box control template means it will participate in mouse events on the rich text as though it were part of the rich text itself.
To work around this problem, you can use a ContentControl that includes the associated elements and then forwards its Content property to a "normal" TextBoxBase derived element. Here is a simplified XAML example. The first section reproduces the strange behavior in a simpler example and the second section shows how to use a ContentControl to work around the problem.
<Grid>
<StackPanel>
<TextBox Text="Some text">
<TextBox.Template>
<ControlTemplate TargetType="TextBox">
<StackPanel>
<Rectangle Fill="Green" Width="200" Height="50"/>
<Border x:Name="RedBorder" PreviewMouseDown="Border_PreviewMouseDown" Background="Red">
<AdornerDecorator x:Name="PART_ContentHost" />
</Border>
</StackPanel>
</ControlTemplate>
</TextBox.Template>
</TextBox>
<ContentControl Content="Some text">
<ContentControl.Template>
<ControlTemplate TargetType="ContentControl">
<StackPanel>
<Rectangle Fill="Green" Width="200" Height="50"/>
<Border x:Name="RedBorder" PreviewMouseDown="Border_PreviewMouseDown" Background="Red">
<TextBlock Text="{TemplateBinding Content}"/>
</Border>
</StackPanel>
</ControlTemplate>
</ContentControl.Template>
</ContentControl>
</StackPanel>
</Grid>
You're missing bubbling events here. http://msdn.microsoft.com/en-us/library/ms742806.aspx#routing_strategies
You can check RoutedEventArgs.OriginalSource to determine which border was clicked.
UPDATE: Ok, it looks like I missed, but not completely. I've played for some time with your sample, it looks like RichTextBox (or probably TextBoxBase) does something with PART_ContentHost which forces tunnelling events to go towards this part. Your sample behaves well out of RichTextBox so I assume it does something with own template. Though I do not how to investigate it further without going into .Net sources.

Resources