Can a user control style its parent window? - wpf

I have a user control that is displayed in a popup dialog using Prism's PopupWindowAction. I don't want the window to be resizeable. Is it possible to style this window from the user control? I attempted to use this:
<UserControl.Resources>
<Style x:Key="WindowStyle" TargetType="{x:Type Window}">
<Setter Property="ResizeMode" Value="NoResize" />
</Style>
</UserControl.Resources>
but it is not working.
Edit:
Based on the accepted answer, I moved the style over to the user control that is defining the IteractionRequestTrigger and assigning the PopupWindowAction's WindowStyle.
New code in calling use control:
Add Resource
<UserControl.Resources>
<Style x:Key="WindowStyle" TargetType="Window">
<Setter Property="ResizeMode" Value="NoResize" />
<Setter Property="SizeToContent" Value="WidthAndHeight" />
</Style>
</UserControl.Resources>
Popup Window declaration
<prism:InteractionRequestTrigger SourceObject="{Binding InteractionRequest}">
<prism:PopupWindowAction WindowStyle="{StaticResource WindowStyle}">
<prism:PopupWindowAction.WindowContent>
<sharedV:InformationDialog />
</prism:PopupWindowAction.WindowContent>
</prism:PopupWindowAction>
</prism:InteractionRequestTrigger>

The reason why your XAML doesn't work is:
The style you created is explicit - it has a x:Key. Explicit styles have to be applied on the target element directly <Window Style="{StaticResource WindowStyle}" ... />
Even if you remove the x:Key and make it an implicit style, since it is defined in the UserControl's resources, it will only apply to items below the UserControl in the tree.
In this case you might want to look at the WindowStyle property of the PopupWindowAction. You should be able to set the style there.

Children cannot directly change their parent's style since the visual tree model is designed to go from broad to specific (Window to UserControl...) with styles being inherited and overridden in that direction. That said, anything is possible because it's all just code!
This isn't a nice way to do it, but you can use the Loaded method of your UserControl in the code behind to do the work of navigating the visual tree to find the parent Window and forcibly set the ResizeMode property. You can do this using this.Parent and checking for when is Window is true, or you can use VisualTreeHelper.GetParent.
XAML:
<UserControl Loaded="OnLoaded"></UserControl>
C#:
private void OnLoaded(object sender, RoutedEventArgs e)
{
var currentParent = Parent;
while (currentParent != null && !(currentParent is Window))
{
currentParent = VisualTreeHelper.GetParent(currentParent);
}
if (currentParent is Window parentWindow)
{
parentWindow.ResizeMode = ResizeMode.NoResize;
}
}

Related

WPF Style Hierarchy

I have created a custom control, ColorToggleButton, which inherits ToggleButton. In a corresponding .xaml file, a for ColorToggleButton is specific via TargetType and BasedOn ToggleButton.
<Style TargetType="ctl:ColorToggleButton" BasedOn="{StaticResource {x:Type ToggleButton}}">
This works fine, but if I apply another style in a window using x:Key, as in
<Style x:Key="SameContent"><Setter Property="Content" Value="Same Content" /></Style>
<ctl:ColorToggleButton Style={StaticResource SameContent} />
the old style seems to get wiped out completely and replaced with the new one. I can circumvent the problem by using BasedOn
<Style x:Key="SameContent" BasedOn="{StaticResource {x:Type ctl:ColorToggleButton}}"><Setter Property="Content" Value="Same Content" /></Style>
<ctl:ColorToggleButton Style={StaticResource MyKey} />
but this seems counterintuitive to me, seeing as I wouldn't use the BasedOn attribute if I was applying styles to a normal ToggleButton or some other default control. Is this the standard way of implementing your own controls? Am I doing something horribly wrong?
Edit: The static constructor of ColorToggleButton is as follows:
static ColorToggleButton()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(ColorToggleButton), new FrameworkPropertyMetadata(typeof(ColorToggleButton)));
}
In your control, did you provide static constructor with DefaultStyleKeyProperty override?
static ColorToggleButton()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(ColorToggleButton), new FrameworkPropertyMetadata(typeof(ColorToggleButton)));
}

How to get TreeViewItems to not inherit Tooltip property from their parent

I have an application in which is a quasi IDE where a TreeView is acting as a solution explorer. What the user is designer is a screen layout which could look like this.
Root
Menus
MainMenu
MenuItem1
Button Bars
MainBar
Button1
I originally had issues with context menus. In the example above MenuItem1 doesn't have a context menu but MainMenu does. Well, MenuItem1 would inherit the context menu from MainMenu. I got by this by creating an empty context menu and assigning it to MenuItem1. I'd like something more elegant though.
I have the same issues with tooltips. If I assign one to MainMenu then MenuItem1 inherits the one assigned to MainMenu. I tried setting the MenuItem1 tooltip to null, did nothing. If I set it to "", an empty string it overrides the MainMenu tooltip but when you hover over MenuItem1 a small empty tooltip box appears. I thought the system would have been smart enough to not show the box if it was an empty string but apparently not.
How can I prevent children from inheriting context menu and tooltip properties from their parents?
Updated
Still having issues with this. I analyzed my items using Snoop and it indicates that these properties are inheirited, but I still don't see any solution to breaking the inheritance.
The only kludge I can think of is that for every tooltip to handle the ToolTipOpening event and inspect the string, if it has no length then jsut close it immediately. There must be a better way though.
I ran into the exact same problem but I found a solution that works for me. I changed the visibility of the tooltip so that it no longer appears for empty strings.
System.Windows.Controls.ToolTip tt = new System.Windows.Controls.ToolTip();
tt.Content = tooltipDescription;
if (tooltipDescription == null)
tt.Visibility = Visibility.Collapsed;
item.ToolTip = tt;
Have you tried setting ToolTipService.IsEnabled="False" this will disable your Tooltip on the desired element.
For myself, I created a style with zero Width and Height:
<Style x:Key="NullToolTip" TargetType="{x:Type ToolTip}">
<Setter Property="Width" Value="0" />
<Setter Property="Height" Value="0" />
<Setter Property="Content" Value="{x:Null}" />
</Style>
When I created ToolTip with this style and placed in resources:
<ToolTip x:Key="NoToolTip" Style="{StaticResource NullToolTip}" />
Then set this ToolTip for each item:
<TreeViewItem Header="Sample" ToolTipService.ToolTip="{StaticResource NoToolTip}">
or in style:
<Setter Property="ToolTipService.ToolTip" Value="{StaticResource NoToolTip}" />
In this case, null ToolTip for item will be default, but when you set our ToolTip, it will be defined only for him.
Firstly masking a tooltip should be done with null and not string.empty. Secondly if you had used hierarchical data template and itemssource binding for your treeview then you could have set tooltips based on your template hierachy (such as bound to a property in your object hierarchy from model or itemssource) in which case they must had taken an effect based on your specific treeview item.
As of now you can use null to mask.
The other answers here all had issues for me so here's the approach I came up with which does avoid child tree items showing the parent item's tip.
Similar to some other answers, I use a style with a setter for the Tooltip property. The key differences are:
Binding the Visibility of a ToolTip element instead of the TextBlock element showing the tip.
Wrapping the TextBlock with a Border element. This avoided occasionally seeing a tiny, empty tip block.
<local:StringToVisibilityConverter x:Key="strToVisibilityConverter"/>
<Style x:Key="MyTreeStyleKey" TargetType="TreeViewItem">
<Setter Property="ToolTip">
<Setter.Value>
<ToolTip Visibility="{Binding TipText, Converter={StaticResource strToVisibilityConverter}}">
<Border>
<TextBlock Text="{Binding TipText}"/>
</Border>
</ToolTip>
</Setter.Value>
</Setter>
</Style>
StringToVisibilityConverter is a simple converter I created which returns Visibility.Collapsed for null or emptry strings, Visibility.Visible otherwise.
<[YourControl].Resources>
<Style TargetType="ToolTip" x:Key="InvisibleToolTip">
<Setter Property="Visibility"
Value="Collapsed" />
</Style>
</[YourControl].Resources>
<[YourControl].ToolTip>
<ToolTip Style="{StaticResource InvisibleToolTip}"/>
</[YourControl].ToolTip>

In WPF, how do I find an element in a template that's switched in via a trigger?

I have a UserControl (not a lookless custom control) which, depending on some custom state properties, swaps in various ContentTemplates, all defined as resources in the associated XAML file. In the code-behind, I need to find one of the elements in the swapped-in ContentTemplates.
Now in a lookless control (i.e. a custom control), you simply override OnApplyTemplate then use FindName, but that override doesn't fire when the ContentTemplate gets switched by a trigger (...at least not for a UserControl. I haven't tested that functionality with a custom control.)
Now I've tried wiring up the Loaded event to the control in the swapped-in template, which does fire in the code-behind, then I simply store 'sender' in a class-level variable. However, when I try to clear that value by subscribing to the Unloaded event, that doesn't fire either because the tempalte gets swapped out, thus unwiring that event before it has a chance to be called and the control unloads from the screen silently, but I still have that hung reference in the code-behind.
To simulate the OnApplyTemplate functionality, I'm considering subscribing to the ContentTemplateChanged notification and just using VisualTreeHelper to look for the control I want, but I'm wondering if there's a better way, hence this post.
Any ideas?
For reference, here's a very-stripped-down example of the control I have. In this example, if IsEditing is true, I want to find the textbox named 'FindMe'. If IsEditing is false which means the ContentTemplate isn't swapped in, I want to get 'null'...
<UserControl x:Class="Crestron.Tools.ProgramDesigner.Controls.EditableTextBlock"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Crestron.Tools.ProgramDesigner.Controls"
x:Name="Root">
<UserControl.Resources>
<DataTemplate x:Key="EditModeTemplate">
<TextBox x:Name="FindMe"
Text="{Binding Text, ElementName=Root}" />
</DataTemplate>
<Style TargetType="{x:Type local:EditableTextBlock}">
<Style.Triggers>
<Trigger Property="IsEditing" Value="True">
<Setter Property="ContentTemplate" Value="{StaticResource EditModeTemplate}" />
</Trigger>
</Style.Triggers>
</Style>
</UserControl.Resources>
<TextBlock x:Name="TextBlock"
Text="{Binding Text, ElementName=Root}" />
</UserControl>
Aaaaaaand GO!
M
Unfortunately, there isn't a better way. You can override the OnContentTemplateChanged, instead of hooking up to the event.
You would need to use the DataTemplate.FindName method to get the actual element. The link has an example of how that method is used.
You would need to delay the call to FindName if using OnContentTemplateChanged though, as it is not applied to the underlying ContentPresenter immediately. Something like:
protected override void OnContentTemplateChanged(DataTemplate oldContentTemplate, DataTemplate newContentTemplate) {
base.OnContentTemplateChanged(oldContentTemplate, newContentTemplate);
this.Dispatcher.BeginInvoke((Action)(() => {
var cp = FindVisualChild<ContentPresenter>(this);
var textBox = this.ContentTemplate.FindName("EditTextBox", cp) as TextBox;
textBox.Text = "Found in OnContentTemplateChanged";
}), DispatcherPriority.DataBind);
}
Alternatively, you may be able to attach a handler to the LayoutUpdated event of the UserControl, but this may fire more often than you want. This would also handle the cases of implicit DataTemplates though.
Something like this:
public UserControl1() {
InitializeComponent();
this.LayoutUpdated += new EventHandler(UserControl1_LayoutUpdated);
}
void UserControl1_LayoutUpdated(object sender, EventArgs e) {
var cp = FindVisualChild<ContentPresenter>(this);
var textBox = this.ContentTemplate.FindName("EditTextBox", cp) as TextBox;
textBox.Text = "Found in UserControl1_LayoutUpdated";
}

WPF: How do I create a button "template" that applies a style & has an image in it?

There are multiple places in my WPF application where I need a button that looks & feels like a regular button, but:
It shows a specific icon on it (defined as {StaticResource EditIcon})
It applies a style (defined as {StaticResource GrayOutButtonStyle})
I would prefer to define these attributes in a single location, rather than repeating them each place the button is used. What is the appropriate way to do this in XAML?
--
If it helps, below is what I'm currently doing, but I was told this is wrong:
Updated: Is this the wrong way? Is there a way to fix this so that it is the "right way"?
I define the button as a template with the key EditButton:
<ControlTemplate x:Key="EditButton" TargetType="{x:Type Button}">
<Button Style="{StaticResource GrayOutButtonStyle}"
Command="{TemplateBinding Command}">
<Image x:Name="editImage" Source="{StaticResource EditIcon}" />
</Button>
</ControlTemplate>
Then, I declare a button with the template EditButton each place I want to use it in the application. I also indicate the Command to invoke here:
<Button Template="{StaticResource EditButton}" Command="..." />
Is this not right? What would be the correct way to do this?
A different approach:
Have you considered making a custom control? This way, you can create your own attributes to set the image contained in the button, and don't have to rely on multiple styles.
<myControl:MyButton x:Name="oneButton" ImageSource="MyButton.png" />
<myControl:MyButton x:Name="anotherButton" ImageSource="MyOtherButton.png" />
class MyButton {
private string imageSource;
public string ImageSource {
get {
return this.imageSource;
}
set {
this.imageSource = value;
//set the image control's source to this.imageSource
}
}
}
You can create a Style which targets all the Button of your app. Do do that, simply create a Style without giving it a Key:
<Style TargetType={x:Type Button}>
</Style>
Then in the Style, you can add a setter which sets the Template property:
<Setter Property="Template">
<Setter.Value>
<!-- whatever you want -->
</Setter.Value>
</Setter>

How can I apply a style to the Window Control in WPF?

I am setting a style for the Window in the App.xaml like such:
<Application x:Class="MusicRepo_Importer.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:System="clr-namespace:System;assembly=mscorlib" StartupUri="TestMaster.xaml">
<Application.Resources>
<Style TargetType="Window">
<Setter Property="WindowStyle" Value="None"></Setter>
</Style>
</Application.Resources>
</Application>
With which I basically want every Window to have its WindowStyle's property value set to None (to remove the default windows frame and border); But it is not working.
What am I missing here?
I believe you have to name the style and apply it to each window like the following..
In app.xaml/resources..
<Style x:Key="MyWindowStyle" TargetType="Window">
<Setter Property="WindowStyle" Value="None"></Setter>
</Style>
Then in the window.xaml..
<Window x:Class="MusicRepo_Importer.MyWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
Title="MyStyledWindow" Style="{StaticResource MyWindowStyle}">
This should work, but simply applying the style with TargetType for Window in the resource won't force the Window to use that style although it seems to work for other elements.
Edit:
Found some info in relation to applying default styles to a window element..
If you supply a TargetType, all
instances of that type will have the
style applied. However derived types
will not... it seems. <Style
TargetType="{x:Type Window}"> will not
work for all your custom
derivations/windows. <Style
TargetType="{x:Type local:MyWindow}">
will apply to only MyWindow. So the
options are
Use a Keyed Style that you specify as
the Style property of every window you
want to apply the style. The designer
will show the styled window.
From the Question: How to set default WPF Window Style in app.xaml?
The person who answered the question had a interesting idea about inheriting from a base window that has the style applied.
I know this question is quite old but I will answer anyway.
Here is the code that works fine for me in C# 4.0.
It just duplicates style for all subclasses in the resource dictionary.
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
if (this.Resources.Contains(typeof(Window)))
{
var types = Assembly.GetEntryAssembly().GetTypes();
var subTypes = types.Where(x => x.IsSubclassOf(typeof(Window)));
Style elementStyle = (Style)this.Resources[typeof(Window)];
foreach (Type subType in subTypes)
{
if (!this.Resources.Contains(subType))
{
this.Resources.Add(subType, elementStyle);
}
}
}
base.OnStartup(e);
}
}
Now your style from App.xaml should work for all windows.
p.s. Yeah, I know this is not the cleanest or fastest way but it works. :)

Resources