How to access UI control from code-behind of ResourceDictionary - c#

So I have a ResourceDictionary to define my custom Window style. What I am struggling to do is to access controls from XAML file.
The ResourceDictionary looks like this
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style x:Key="MyCustomWindowStyle" TargetType="{x:Type Window}">
<Setter Property="WindowChrome.WindowChrome">
<Setter.Value>
<WindowChrome CaptionHeight="30"/>
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Window}">
<Grid>
<!-- the root window -->
<Border BorderThickness="0.3" BorderBrush="{DynamicResource GeneralDarkBlue}">
<AdornerDecorator>
<ContentPresenter />
</AdornerDecorator>
</Border>
<DockPanel Height="30" Background="{TemplateBinding Background}" VerticalAlignment="Top" LastChildFill="False">
<Viewbox x:Name="HamburgerMenu" DockPanel.Dock="Left" WindowChrome.IsHitTestVisibleInChrome="True">
<Viewbox.InputBindings>
<MouseBinding MouseAction="LeftClick" Command="{Binding SettingsClick}"/>
</Viewbox.InputBindings>
<Border Width="47" Height="32" Background="Transparent">
<Canvas>
<Path x:Name="TopbarIconHamburgerMenu" Margin="14,10" Data="M12.5,19h19.2v1H12.5V19z M12.5,13.7h19.2v1H12.5V13.7z M12.5,8.5h19.2v1H12.5V8.5z" Stretch="UniformToFill" Fill="#FFFFFF"/>
</Canvas>
</Border>
</Viewbox>
// the rest of viewboxes for minimize, maximize controls...
</DockPanel>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
And let's say I want to access the HamburgerMenu, so I do something like this
public partial class MyCustomWindowStyle : ResourceDictionary
{
public MyCustomWindowStyle()
{
InitializeComponent();
}
public void DoSomething()
{
var window = (Style)Application.Current.Resources["MyCustomWindowStyle"];
var hm = (Viewbox)window.Resources.FindName("HamburgerMenu");
}
}
and this returns null in the hm!
Any idea how to do this?

First of all, Style.Resource is a ResourceDictionary, and there are two important things to notice in the ResourceDictionary.FindName method documentation:
Summary section saying:
Not supported by this Dictionary implementation.
and Return Value section saying:
Always returns null.
Second of all, even if you tried to retrieve the ViewBox by key, it would have to be defined as a resource:
<Style x:Key="MyCustomWindowStyle" TargetType="{x:Type Window}">
<Style.Resources>
<ViewBox x:Key="HamburgerMenu" />
</Style.Resources>
</Style>
And it is not. It is a part of ControlTemplate's visual tree.
Third of all, ControlTemplate does not contain actual elements, but rather a recipe for creating them. So there's no actual ViewBox living inside the ControlTemplate to retrieve. Notice that ControlTemplate.FindName takes an additional parameter specifying an element for which the template was realized.
However, ControlTemplate does have a LoadContent method, which basically loads the visual tree defined by that template, and I think you could use it, and then invoke FindName on the root element. To simplify retrieval of the ControlTemplate let's first make it a resource:
<Style x:Key="MyCustomWindowStyle" TargetType="{x:Type Window}">
<Style.Resources>
<ControlTemplate x:Key="Template" TargetType="{x:Type Window}">
<Grid>
(...)
<ViewBox x:Key="HamburgerMenu" />
(...)
</Grid>
</ControlTemplate>
</Style.Resources>
<Setter Property="Template" Value="{StaticResource Template}" />
</Style>
Then this should do the trick for you:
var window = (Style)Application.Current.Resources["MyCustomWindowStyle"];
var template = (ControlTemplate)window.Resources["Template"];
var root = (FrameworkElement)template.LoadContent();
var hm = (ViewBox)root.FindName("HamburgerMenu");
Update
If your goal is to get hold of the ViewBox in an existing window with that template applied, first you need to know how to get hold of that particular window. It could be the Application.Current.MainWindow, otherwise you're highly likely to find it in the Application.Current.Windows collection. You could also implement the singleton pattern for that window, or use other methods like exposing a static property with reference to that window somewhere in your application, or using third-party tools, such as Service Locator in Prism.
Once you have the window in your hand, you only need to use the previously mentioned ControlTemplate.FindName method:
var window = (...);
var hm = (ViewBox)window.Template.FindName(name: "HamburgerMenu", templatedParent: window);
Note that accessing the resource dictionary in which the template was defined is not necessary.
As for why your attempts with previous solution failed - that's because ControlTemplate.LoadContent method yields freshly created element each time it is invoked, and modifying it does not reflect on elements previously created by that template.

Related

Wpf - how to create a library of custom control to use and customize in differente application

I am creating a collection of custom controls in a project MyLibrary.UI. What I want to achieve is to define the some properties in a component that can be customize in every main app that uses MyLibrary.UI.
I wanto to make an example of customizing an Icon in the control FilteredComboBox.
I tried two ways:
I added a DependencyProperty FindImage defined in FilteredComboBox.cs:
public class FilteredComboBox : ComboBox
{
...
#region FindImageProperty
public static readonly DependencyProperty FindImageProperty = DependencyProperty.Register(
nameof(FindImage), typeof(BitmapImage),
typeof(FilteredComboBox),
new FrameworkPropertyMetadata
{
BindsTwoWayByDefault = true,
DefaultUpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged,
DefaultValue = new BitmapImage(new Uri("pack://application:,,,/MyLibrary.Icons;component/Icons/Find.png"))
});
public BitmapImage FindImage
{
get
{
return (BitmapImage)GetValue(FindImageProperty);
}
set
{
SetValue(FindImageProperty, value);
}
}
#endregion FindImage
static FilteredComboBox()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(FilteredComboBox), new FrameworkPropertyMetadata(typeof(FilteredComboBox)));
}
...
}
and modified the style FilteredComboBoxStyle.xaml as below:
<ControlTemplate x:Key="FilteredComboBoxTemplate" TargetType="{x:Type local:FilteredComboBox}">
...
<DockPanel>
<Image Source="{TemplateBinding FindImage}" Width="25" Height="25" DockPanel.Dock="Left"/>
<TextBox x:Name="PART_SearchTextBox" Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=SearchText, UpdateSourceTrigger=PropertyChanged}" VerticalContentAlignment="Center" DockPanel.Dock="Left"/>
</DockPanel>
...
</ControlTemplate>
<Style TargetType="{x:Type local:FilteredComboBox}" x:Key="baseFilteredCBStyle">
...
<Setter Property="Template" Value="{StaticResource FilteredComboBoxTemplate}"/>
...
</Style>
<Style TargetType="{x:Type local:FilteredComboBox}" BasedOn="{StaticResource baseFilteredCBStyle}"/>
Then I added the reference of this control style in Themes/generic.xaml and I defined in the resources of App.xaml of my application the following style:
<Style TargetType="{x:Type local:FilteredComboBox}" BasedOn="{StaticResource baseFilteredCBStyle}">
<Setter Property="FindImage">
<Setter.Value>
<BitmapImage x:Key="myImage" Source="pack://application:,,,/MyCustomApp.Icons;component/Icons/Find.png"/>
</Setter.Value>
</Setter>
</Style>
I would expect that this would change the icon with MyCustomApp.Icons, but it still keeps the Icon in MyLibrary.Icons.
Then I tried to use a DynamicResource to set the image, so in FilteredComboBoxStyle.xaml:
<BitmapImage x:Key="myImage" Source="pack://application:,,,/MyLibrary.Icons;component/Icons/Find.png"/>
<ControlTemplate x:Key="FilteredComboBoxTemplate" TargetType="{x:Type local:FilteredComboBox}">
...
<DockPanel>
<Image Source="{DynamicResource myImage}" Width="25" Height="25" DockPanel.Dock="Left"/>
<TextBox x:Name="PART_SearchTextBox" Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=SearchText, UpdateSourceTrigger=PropertyChanged}" VerticalContentAlignment="Center" DockPanel.Dock="Left"/>
</DockPanel>
...
</ControlTemplate>
<Style TargetType="{x:Type local:FilteredComboBox}">
...
<Setter Property="Template" Value="{StaticResource FilteredComboBoxTemplate}"/>
...
</Style>
Then in the resources of App.xaml:
<BitmapImage x:Key="myImage" Source="pack://application:,,,/MyCustomApp.Icons;component/Icons/Find.png"/>
With this approach my application shows the icon from MyCustomApp.Icons.
My questions are:
Why the option 1 is not working? There is something wrong with it?
Is the option 2 the proper way to customize the custom controls to be application-specific?
I made an example with an Image but could be any property of a control.
The Themes/generic.xaml resource dictionary should be located in the project where the custom control is defined.
If you want to set the FindImage property using an implicit style in the consuming application, you should put the Style in App.xaml or in a resource dictionary that is merged into App.xaml:
<Application x:Class="WpfApp1.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
...>
<Application.Resources>
<ResourceDictionary>
<Style TargetType="{x:Type local:FilteredComboBox}" BasedOn="{StaticResource baseFilteredCBStyle}">
<Setter Property="FindImage">
<Setter.Value>
<BitmapImage Source="pack://application:,,,/MyCustomApp.Icons;component/Icons/Find.png"/>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
</Application.Resources>
</Application>
Your question is very difficult because it is full of typos and compiler errors. It's hard to tell where you did wrong and where you were just sloppy when creating the example. Maybe you should create the question more carefully next time.
Option 1 is not working because you can't reference a resource defined in Generic.xaml from your application. Generic.xaml is meant as a theme dictionary for theme resources, like the default control Style of FileteredComboBox.
This means you can't base a Style defined in App.xaml on an explicitly named resource that is defined in Generic.xaml.
Option 2 is working because there is no such an illegal reference to a resource that is defined in Generic.xaml. The Style in Generic.xaml references a resource (the BitmapImage) using the DynamicResource markup. The lookup behavior of this markup (opposed to StaticResource) occurs at runtime: once the application is running, lookup starts from the location of the referencing element and traverses up the logical tree to visit every ResourceDictionary along the route. Then the XAML engine checks the App.xaml and finally Generic.xaml for the requested resource.
In your particular case the lookup starts directly at App.xaml, where the XAML engine successfully finds the BitmapImage with the matching key.
To fix your issue, you must base the Style defined within the application scope on the target type (which will implicitly translate to the default Style of the styled control):
BasedOn="{StaticResource {x:Type local:FilteredComboBox}}"
App.xaml
<Style TargetType="{x:Type local:FilteredComboBox}"
BasedOn="{StaticResource {x:Type local:FilteredComboBox}}">
<Setter Property="FindImage">
<Setter.Value>
<BitmapImage x:Key="myImage"
Source="pack://application:,,,/MyCustomApp.Icons;component/Icons/Find.png" />
</Setter.Value>
</Setter>
</Style>
I have to point out that it is not necessary to base your Style on the default Style explicitly.
The default Style is always loaded first. Any additional Style that targets the same control will be applied commulative i.e it will be "merged" into the default style: unless FrameworkElement.OverridesDefaultStyle is set to true, only the duplicate property setters are overwritten. Otherwise the default Style will be completely ignored (overridden).
In other words, application scope styles are always implicitly based on the control's default Style.

Bind to Application.ActualTheme in a ControlTemplate XAML UWP

I'm trying to create a control that uses a RevealBorderBrush as its border brush in XAML. I want to use the correct TargetTheme value for the brush, so I'm trying to bind to my application's current ActualTheme value. I'm using a templated control to do this. My C# code behind file is just the empty constructor that inherits from Control and sets the default style key. The following is my Generic.xaml
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Plank">
<Style TargetType="local:PlankPL" >
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:PlankPL">
<Border
Background="{TemplateBinding Background}"
BorderThickness="{TemplateBinding BorderThickness}">
<Border.BorderBrush>
<RevealBorderBrush TargetTheme="{Binding Source=local:App, Path=ActualTheme}"/>
</Border.BorderBrush>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
I'm pretty sure the binding statement is incorrect, but I'm not sure how to write it.
I haven't tested this but it should work. Name your control then use the below code, replace ControlTemplateName with what you used.
You can’t bind background data in ResourceDictionary, it doesn’t work.
It is recommended to write a UserControl and bind the RevealBorderBrush property of Border like following.
MyUserControl1.xaml:
<UserControl..>
<StackPanel>
<Border BorderThickness="3">
<Border.BorderBrush>
<RevealBorderBrush TargetTheme="{x:Bind theme1}"/>
</Border.BorderBrush>
</Border>
<TextBox Text = "text demo text "/>
</StackPanel>
</UserControl>
MyUserControl1.xaml.cs:
public sealed partial class MyUserControl1 : UserControl
{
private ApplicationTheme theme1;
public MyUserControl1()
{
this.InitializeComponent();
theme1 = Application.Current.RequestedTheme;
}
}

How to globally change the styling of the dottet lines around all focusble elements in one place?

From here I learned how to change the styling of dotted lines around focused button. I would like to apply the same thing on all focus-able elements of the current WPF application (or if not possible current page) in one place ( not doing separately for focus-able buttons, textboxes etc)
PS. Preferably in code behind
Define this Style in your App.Xaml .
<Application.Resources>
<Style x:Key="StyleFocusDefault" TargetType="{x:Type Control}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Control}">
<Grid>
<Rectangle StrokeThickness="2" Stroke="Black" StrokeDashArray="2"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Application.Resources>
And then you can apply like : < ... FocusVisualStyle="{DynamicResource StyleFocusDefault}" .../>
This will change FocusVisualStyle for all Controls who have FocusVisualStyle property. You can further experiment with this appaorach for various controls.

Removing borders from AvalonDock panel

here is whats getting on my nerve:
My job si to restyle application, so i didn’t wrote it, i have to slightly change the code(.cs or .xaml). Problem lies within avalonDock…i cant remove borders from panels, or change its color.
This is part of code, where dockingManager parts are defined(lets call it MainView.xaml)
<ad:DockingManager Name="dockingManager" >
<ad:ResizingPanel Orientation="Horizontal">
<ad:DockablePane ad:ResizingPanel.ResizeWidth="50" Name="navigatorHostCtrlPane" >
<ad:DockableContent Name="navigatorHostCtrl" Title="{StaticResource Navi}" IsCloseable="False" Background="Transparent"/>
</ad:DockablePane>
<ad:ResizingPanel Orientation="Vertical" >
<ad:DockablePane Name="mainPane" >
<ad:DockableContent x:Name="mainHostCtrl" Title="{StaticResource Sc}" AllowDrop="False" IsCloseable="False" ClipToBounds="False" Background="White"/>
</ad:DockablePane>
<ad:DockablePane ad:ResizingPanel.ResizeHeight="250" >
<ad:DockableContent Name="dataHostCtrl" Title="{StaticResource Dt}" IsCloseable="False" Background="White"/>
</ad:DockablePane>
</ad:ResizingPanel>
</ad:ResizingPanel>
</ad:DockingManager>
And this is where parts are set in .cs file(MainView.xaml.cs) like this:
BindRegionToGui(regionManager, RegionNames.NavigatorRegion, navigatorHostCtrl);
And BindRegionToGui():
private static void BindRegionToGui(IRegionManager regionManager, string regionName, UIElement content)
{
var reg = (AvalonDockRegion)regionManager.Regions[regionName];
reg.Bind(content);
}
I cant change style of outer border even in element by setting BorderThickness = “1“, or in style definition:
<Style x:Key="{x:Type ad:DockablePane}" TargetType="{x:Type ad:DockablePane}">
<Setter Property="Background" Value="Transparent"/>
<Setter Property=“BorderThickness“ Value=“0“/>
</Style>
I can alter whole dockingPanes by setting style like this(after some example):
<Style TargetType="{x:Type ad:DockablePane}">
…
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ad:DockablePane}">
<Border
Background=….
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
But with this – the content won’t show up. I assume, that content.template overrides it, but i dont know how i can put application’s defined panels into it.
Sorry if this is stupid question but i am really beginner in xaml / c# so i’m in kind of a bad situation.
I restyled AvalonDock in pretty dumb way. I've downloaded source code from http://avalondock.codeplex.com/SourceControl/list/changesets, changed generic.xaml and rebuild whole library.

Issue with applying style on WPF UserControl

I have a user-control and I want to use it in some other project. There is no problem when I set some value to its properties directly:
<local:MyUserControl prop1="val1" prop2="val2">
...
</local:MyUserControl>
But I can't apply a style to it. I tried:
<Window ...>
<Window.Resources>
<Style x:Key="MyUserControlStyle" TargetType="{x:Type local:MyUserControl}">
<Setter Property="prop1" Value="val1"/>
<Setter Property="prop2" Value="val2"/>
</Style>
</Window.Resources>
<Grid>
<local:MyUserControl Style="{StaticResource ResourceKey=MyUserControlStyle}">
...
</local:MyUserControl>
</Grid>
</Window>
Where did I wrong? -Thanks
Using dear #Mario Vernari's instructions, I found it out that the problem was due to a bad strategy which I'd used to create my UserControl. I wanted to create a UserControl that be able to hold some other ones. So I had tried this:
<UserControl x:Class="MyNamespace.MyUserControl"
...
Style="{DynamicResource ResourceKey=MyUserControlStyle}">
<UserControl.Resources>
...
<Style x:Key="MyUserControlStyle" TargetType="{x:Type UserControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type UserControl}">
<Border BorderBrush="{Binding Path=DP1}">
...
<ContentPresenter ... Content="{TemplateBinding Content}"/>
...
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
</UserControl>
Where DP1 is a dependency property of type Brush. The UserControl which has been created through this way works if you set its properties (like DP1) directly. Absolutely this is not the true way as #Mario told me:
...When you use an UserControl, it means that you already know its layout, and there is no need to style the control further. You are defining its style twice at the same time thus results a collision...
And he added:
Instead, you should use a CustomControl; Define the default style in the Themes folder (if you own regular Visual Studio, it makes automatically). Afterward, you may override the default style in your own app. In the same way you would do for a normal base class and its derived.
Follow this:
http://www.codeproject.com/KB/WPF/WPFCustomControl.aspx ...
Obviously, in this case we need to derive our lookless control from ContentControl class (instead of Control class). You may take a look at this & this to master the details.
Here, I give thanks to #Mario again. ;)
You are giving Style="{StaticResource ResourceKey=MyUserControlStyle}".
It's just - Style="{StaticResource MyUserControlStyle}".

Categories

Resources