Height setter ignored on custom Window - wpf

I added a Custom Control to my project and changed the parent type to Window. I show it on a button click.
I have style setters for height and width, but only the one defined first in the xaml has effect. The other shows larger than styled.
Anyone know what's happening here?
In generic.xaml:
<Style TargetType="{x:Type local:ChildWindow}">
<Setter Property="Width" Value="300"/>
<Setter Property="Height" Value="300"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:ChildWindow}">
<TextBlock Background="White">Child window</TextBlock>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
ChildWindow.cs is default except the parent is now "Window":
public class ChildWindow : Window
{
static ChildWindow()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(ChildWindow), new FrameworkPropertyMetadata(typeof(ChildWindow)));
}
}
MainWindow.xaml.cs shows the ChildWindow:
private void Button_Click(object sender, RoutedEventArgs e)
{
var childWindow = new ChildWindow();
childWindow.Show();
}

Workaround:
I haven't found why it behaves like that, or how to make the style setters work, but a workaround is to get rid of the style setters and set the default values for the Width and Height dependency properties. I did this in the static constructor:
static ChildWindow()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(ChildWindow), new FrameworkPropertyMetadata(typeof(ChildWindow)));
// Get rid of the style setters and add this:
WidthProperty.OverrideMetadata(typeof(ChildWindow), new FrameworkPropertyMetadata(300.0));
HeightProperty.OverrideMetadata(typeof(ChildWindow), new FrameworkPropertyMetadata(300.0));
}

Related

Setting a local style for an adorner

I have an adorner which should be placed beside it's adorned element. Depening on the value of the custom Position dependency property the adorner appears at the left or right side of the element.
I want to use a style to set the value of the Position property. But I can only do this if I add the style to the resources of the top-level control. If I place the style inside the resources of any child element it shows no effect.
Is there a way that I can set the adorner style on a per-element basis like in the following example?
<Window x:Class="StyledAdorner.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:StyledAdorner">
<Window.Resources>
<Style TargetType="local:MyAdorner">
<Setter Property="Position" Value="Right" />
</Style>
<Style TargetType="Button">
<Setter Property="Content" Value="Adorn me!" />
<Setter Property="Margin" Value="15" />
<EventSetter Event="Click" Handler="AddAdorner" />
</Style>
</Window.Resources>
<StackPanel>
<Button />
<Button>
<Button.Resources>
<Style TargetType="local:MyAdorner">
<!-- This setter has no effect! -->
<Setter Property="Position" Value="Left" />
</Style>
</Button.Resources>
</Button>
</StackPanel>
</Window>
The only solution I can image is to scan the adorned element's resources for an adorner style. If there is one then check if there is a setter for the Position property and use this value. But that looks like a really dirty hack...
Code for AddAdorner handler that creates the adorner:
private void AddAdorner(object sender, RoutedEventArgs e)
{
new MyAdorner((UIElement)sender);
}
Constructor for MyAdorner
private Path _indicator = new Path { /* details omitted */ };
public MyAdorner(UIElement adornedElement) : base(adornedElement)
{
AdornerLayer.GetAdornerLayer(AdornedElement).Add(this);
AddVisualChild(_indicator);
InvalidateMeasure();
InvalidateArrange();
}
I could solve my problem by turning the Position property into an attached property:
public static readonly DependencyProperty PositionProperty = DependencyProperty.RegisterAttached(
"Position",
typeof(AdornerPosition),
typeof(MyAdorner),
new FrameworkPropertyMetadata(AdornerPosition.Right, UpdateAdornerLayerLayout));
Now, I just have to set the desired value on the adorned element:
<Button local:MyAdorner.Position="Left">
The property is evaluated in adorner`s ArrangeOverride method when the position for the adorner is calculated.
Note that the UpdateAdornerLayerLayout property changed callback is neccessary to force a layout update for the adorner layer when the property changes:
private static void UpdateAdornerLayerLayout(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is UIElement element)
{
var layer = AdornerLayer.GetAdornerLayer(element);
layer?.Update();
}
}

How to change all TextBox's Foreground Color in an application

In my app (C# WPF) I have about 30 or 40 textBoxes in more grids and I want to change their foreground color in a loop. I use the code below and it works. But I want to use it for the whole project, not only for concrete grid
xaml code
<grid x:Name"stk">
.... some textBoxes ...
</grid>
*.cs code
foreach (TextBox item in this.stk.Children.OfType<TextBox>())
{
if (item.Name.StartsWith("txt"))
item.Foreground = Brushes.Orange;
}
So, when I have more grids, I have to put x:Name="..." into each one and this implies more foreach loops.
Much Simpler Way
Define a Style with TargetType set to Textbox and with no Key. This way this style will be applied to all textbox in the application without the need to bind the style or the foreground for each textbox.
<Application.Resources>
<SolidColorBrush Color="Red" x:Key="txtColor" />
<Style TargetType="TextBox">
<Setter Property="Foreground" Value="{DynamicResource txtColor}" />
</Style>
</Application.Resources>
To change the Foreground Color.
private void Button_Click(object sender, RoutedEventArgs e)
{
if (Application.Current.Resources.Contains("txtColor"))
{
Application.Current.Resources["txtColor"] = new SolidColorBrush(Colors.Blue);
}
}
Bind all your Textbox's Foreground to a common Brush Resource. Define the brush resource common to Project and access it everywhere.
In App.XML declare the brush resource so that you can access it anywhere from your project. [Note : You can also define it resource Dictionary and refer it]
<Application.Resources>
<SolidColorBrush Color="Red" x:Key="txtColor" />
</Application.Resources>
In All your textbox bind the foreground to the "txtColor" brush resource.
<TextBox Foreground="{DynamicResource txtColor}" Text="TextBox" />
To change the Foreground color of all textbox's, then change the commonly defined resource's color. Below I changed the color in button click. Access th resource using the key and set the new brush which you want to set.
private void Button_Click(object sender, RoutedEventArgs e)
{
if (Application.Current.Resources.Contains("txtColor"))
{
Application.Current.Resources["txtColor"] = new SolidColorBrush(Colors.Blue);
}
}
Ignore my code and have a look at this answer
Find all controls in WPF Window by type
So ... To solve my problem where I couldn't change foreground color of textBoxes when some textBox is disabled ... I used code below ...
<Application.Resources>
<Style TargetType="TextBox">
<Style.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Foreground" Value="Orange"/>
</Trigger>
<Trigger Property="IsEnabled" Value="True">
<Setter Property="Foreground" Value="Green"/>
</Trigger>
</Style.Triggers>
</Style>
</Application.Resources>
What about creating a "usercontrol" based on the standard textbox where you control the appearance of the foreground. This way you have a reusable control that you can use anywhere you want and have "full control" over it's appearance and behaviour. Take a look at this article, or this one for some examples that might help you go the right way ;)

Create a custom ListViewItem (subclass ListViewItem)

I derived a class from ListViewItem, it has some custom dependency properties:
public class CustomListViewItem : ListViewItem
{
public static DependencyProperty CustomDependencyProperty;
...
}
There is also a ControlTemplate for this class.
<Style TargetType="{x:Type local:CustomListViewItem}">
<Style.Setters>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:CustomListViewItem}">
...
</ControlTemplate>
</Setter.Value>
</Setter>
</Style.Setters>
</Style>
Now I want to use this CustomListViewItem in a ListView instead of ListViewItem. But when I try to do something like:
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type local:CustomListViewItem}">
...
</Style>
</ListView.ItemContainerStyle>
compiler says: "A style intended for type 'CustomItem' cannot be applied to type 'ListViewItem".
I know that I can use ControlTemplate with ListViewItem TargetType to customize ItemContainerStyle or DataTemplate to customize ItemTemplate, but how can I subclass ListViewItem to substitute my own Item type?
Any help will be appreciated.
I found the answer after considering this question. The core idea is that it is necessary to create not only a custom ListViewItem, but also a custom ListView and override GetContainerForItemOverride() method for it:
public class CustomListView : ListView
{
static CustomListView()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomListView), new FrameworkPropertyMetadata(typeof(CustomListView)));
}
protected override DependencyObject GetContainerForItemOverride()
{
return new CustomListViewItem();
}
}
Of course, it's also necessary to provide a proper ControlTemplate for a CustomListView.
Also PrepareContainerForItemOverride method will be useful.

How to override base style control template in derived style in WPF?

I have a style for button. That style contains the ControlTemplate for Button. The ControlTemplate contains an Image with name "ImgButton".
I want to make this style as base style for other Buttons and want to override the "Source" property of Image control for different buttons.
Any ideas?
You may create attached behavior that will offer a property to assign Source. You should bind your image to this property in a template using TemplatedParent as RelativeSource. In derived styles you can simply use Setter(s) to specify a different Source.
Attached behavoir:
public static class ImageSourceBehavior
{
public static readonly DependencyProperty SourceProperty = DependencyProperty.RegisterAttached(
"Source", typeof(ImageSource), typeof(ImageSourceBehavior),
new FrameworkPropertyMetadata(null));
public static ImageSource GetSource(DependencyObject dependencyObject)
{
return (ImageSource)dependencyObject.GetValue(SourceProperty);
}
public static void SetSource(DependencyObject dependencyObject, ImageSource value)
{
dependencyObject.SetValue(SourceProperty, value);
}
}
Styles:
<Style x:Key="Style1"
TargetType="Button">
<Setter Property="local:ImageSourceBehavior.Source"
Value="..."/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Image Source="{Binding Path=(local:ImageSourceBehavior.Source),RelativeSource={RelativeSource TemplatedParent}}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="Style2"
BasedOn="{StaticResource Style1}"
TargetType="Button">
<Setter Property="local:ImageSourceBehavior.Source"
Value="..."/>
</Style>

How to use IsKeyboardFocusWithin and IsSelected together?

I have a style defined for my ListBoxItems with a trigger to set a background color when IsSelected is True:
<Style x:Key="StepItemStyle" TargetType="{x:Type ListBoxItem}">
<Setter Property="SnapsToDevicePixels" Value="true"/>
<Setter Property="OverridesDefaultStyle" Value="true"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Border Name="Border" Padding="0" SnapsToDevicePixels="true">
<ContentPresenter />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter TargetName="Border" Property="Background" Value="#40a0f5ff"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
This style maintains the selected item even when the ListBox and ListBoxItem loses focus, which in my case is an absolute must.
The problem is that I also want the ListBoxItem to be selected when one of its TextBox's child gets focused. To achieve this I add a trigger that sets IsSelected to true when IsKeyboardFocusWithin is true:
<Trigger Property="IsKeyboardFocusWithin" Value="True">
<Setter Property="IsSelected" Value="True" />
</Trigger>
When I add this trigger the Item is selected when the focus is on a child TextBox, but the first behaviour disappears. Now when I click outside the ListBox, the item is de-selected.
How can I keep both behaviours?
When your listbox looses focus, it will set selected item to null because of your trigger. You can select on focus using some code behind that will not unselect when you loose focus.
XAML:
<Window x:Class="SelectedTest.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="300" Width="300">
<StackPanel>
<TextBox Text="Loose focus here" />
<ListBox Name="_listBox" ItemsSource="{Binding Path=Items}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" GotFocus="OnChildGotFocus">
<TextBox Text="{Binding .}" Margin="10" />
<TextBox Text="{Binding .}" Margin="10" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="SnapsToDevicePixels" Value="true"/>
<Setter Property="OverridesDefaultStyle" Value="true"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Border Name="Border" SnapsToDevicePixels="true" Background="Transparent">
<ContentPresenter />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter TargetName="Border" Property="Background" Value="Red"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
</StackPanel>
</Window>
Code behind:
private void OnChildGotFocus(object sender, RoutedEventArgs e)
{
_listBox.SelectedItem = (sender as StackPanel).DataContext;
}
"When I add this trigger the Item is selected when the focus is on a child TextBox, but the first behaviour disappears. Now when I click outside the ListBox, the item is de-selected."
Actually, I don't think it has lost that original behavior. What I suspect is happening is you're clicking directly in the textbox from somewhere else so the underlying ListBoxItem never actually became selected. If it did however, you'd see the selection would still remain after you left as you want.
You can test this by forcing the ListBoxItem to be selected by clicking directly on it (side-note: you should always give it a background, even if just 'transparent' so it can receive mouse clicks, which it won't if it's null) or even just hitting 'Shift-Tab' to set the focus there, back from the textbox.
However, that doesn't solve your issue, which is that the TextBox gets the focus but doesn't let the underlying ListBoxItem know about it.
The two approaches you can use for that are an event trigger or an attached behavior.
The first is an event trigger on the IsKeyboardFocusWithinChanged event where you set 'IsSelected' to true if the keyboard focus changed to true. (Note: Sheridan's answer does a faux-change-notification but it should not be used in cases where you can multi-select in the list because everything becomes selected.) But even an event trigger causes issues because you lose the multi-select behaviors such as toggling or range-clicking, etc.
The other (and my preferred approach) is to write an attached behavior which you set on the ListBoxItem, either directly, or via a style if you prefer.
Here's the attached behavior. Note: You again would need to handle the multi-select stuff if you want to implement that. Also note that although I'm attaching the behavior to a ListBoxItem, inside I cast to UIElement. This way you can also use it in ComboBoxItem, TreeViewItem, etc. Basically any ContainerItem in a Selector-based control.
public class AutoSelectWhenAnyChildGetsFocus
{
public static readonly DependencyProperty EnabledProperty = DependencyProperty.RegisterAttached(
"Enabled",
typeof(bool),
typeof(AutoSelectWhenAnyChildGetsFocus),
new UIPropertyMetadata(false, Enabled_Changed));
public static bool GetEnabled(DependencyObject obj){ return (bool)obj.GetValue(EnabledProperty); }
public static void SetEnabled(DependencyObject obj, bool value){ obj.SetValue(EnabledProperty, value); }
private static void Enabled_Changed(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var attachEvents = (bool)e.NewValue;
var targetUiElement = (UIElement)sender;
if(attachEvents)
targetUiElement.IsKeyboardFocusWithinChanged += TargetUiElement_IsKeyboardFocusWithinChanged;
else
targetUiElement.IsKeyboardFocusWithinChanged -= TargetUiElement_IsKeyboardFocusWithinChanged;
}
static void TargetUiElement_IsKeyboardFocusWithinChanged(object sender, DependencyPropertyChangedEventArgs e)
{
var targetUiElement = (UIElement)sender;
if(targetUiElement.IsKeyboardFocusWithin)
Selector.SetIsSelected(targetUiElement, true);
}
}
...and you simply add this as a property setter in your ListBoxItem's style
<Setter Property="behaviors:AutoSelectWhenAnyChildGetsFocus.Enabled" Value="True" />
This of course assumes you've imported an XML namespace called 'behaviors' that points to the namespace where the class is contained. You can put the class itself in a shared 'Helper' library, which is what we do. That way, everywhere we want it, its a simple property set in the XAML and the behavior takes care of everything else.
I figured out that IsKeyboardFocusWithin is not the best solution.
What I did in this case was to set the style on all of the controls used as DataTemplate to send the GotFocus-event to be handled in code behind. Then, in code behind, I searched up the visual tree (using VisualTreeHelper) to find the ListViewItem and set IsSelected to true. This way it does not "touch" the DataContext and works just with the View elements.
<Style TargetType="{x:Type Control}" x:Key="GridCellControlStyle">
...
<EventSetter Event="GotFocus" Handler="SelectListViewItemOnControlGotFocus"/>
...
private void SelectListViewItemOnControlGotFocus(object sender, RoutedEventArgs e)
{
var control = (Control)sender;
FocusParentListViewItem(control);
}
private void FocusParentListViewItem(Control control)
{
var listViewItem = FindVisualParent<ListViewItem>(control);
if (listViewItem != null)
listViewItem.IsSelected = true;
}
public static T FindVisualParent<T>(UIElement element) where T : UIElement
{
UIElement parent = element;
while (parent != null)
{
var correctlyTyped = parent as T;
if (correctlyTyped != null)
{
return correctlyTyped;
}
parent = VisualTreeHelper.GetParent(parent) as UIElement;
}
return null;
}

Resources