How can I avoid overriding a window-wide style in WPF? - c#

I'm using the MaterialDesign nuget package for my WPF application.
According to the tutorial, by applying window-wide properties, every element will inherit the MaterialDesign style.
However, if I apply a custom style to an element, that element loses its MaterialDesign style.
I can get around this by applying inline styles, but that is very repetitive and error prone.
I think the picture shows it better:
And here is my xaml:
<Window
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:local="clr-namespace:TestApp"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes" x:Class="TestApp.MainWindow"
mc:Ignorable="d"
TextElement.Foreground="{DynamicResource MaterialDesignBody}"
TextElement.FontWeight="Regular"
TextElement.FontSize="13"
TextOptions.TextFormattingMode="Ideal"
TextOptions.TextRenderingMode="Auto"
Background="{DynamicResource MaterialDesignPaper}"
FontFamily="{DynamicResource MaterialDesignFont}"
Title="MainWindow" Height="450" Width="600">
<!--All of the above is meant to apply Material Design to the entire Window-->
<StackPanel>
<StackPanel.Resources>
<!--This style overrides the Material Design style entirely,
instead of just Margin and Horizontal Alignment-->
<Style x:Key="SpacedButton" TargetType="Button">
<Setter Property="Margin" Value="0 10"/>
<Setter Property="HorizontalAlignment" Value="Center"/>
</Style>
</StackPanel.Resources>
<!--Material Design only works by applying properties directly to elements-->
<Button Content="Button #1" HorizontalAlignment="Center" Margin="0 10"/>
<Button Content="Button #2" Style="{DynamicResource SpacedButton}"/>
<Button Content="Button #3" Style="{DynamicResource SpacedButton}"/>
<Button Content="Button #4" HorizontalAlignment="Center" Margin="0 10"/>
</StackPanel>
</Window>
As you can see, only elements with inline properties keep the MaterialDesign style, but by applying a custom style, the MaterialDesign style is lost.
How can I make sure that MaterialDesign is applied to every element, while still being able to override specific properties with custom styles?
Sorry if some of the terminology is wrong, I'm pretty new to WPF.

Use Style.BasedOn to inherit properties from whichever other style is in scope:
<Style TargetType="{x:Type Button}" BasedOn="{StaticResource {x:Type Button}}">
...etc....
Or a specific one:
<Style x:Key="ThisStyleKey" TargetType="{x:Type Button}" BasedOn="{StaticResource OtherStyleKey}">
...etc....

Related

WPF Custom Toggle button with ContentControl

It seems very hard to achieve something rather trivial in WPF...
I need to design a toggle button with a specific look (and feel). I made a small project to demonstrate the problem.
"ToggleButton user control" :
<UserControl x:Class="WpfApp4.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="450" d:DesignWidth="800"
Name="Bla">
<UserControl.Resources>
<Style TargetType="ToggleButton">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Grid>
<Ellipse Width="300" Height="300" Fill="Yellow"/>
<ContentPresenter />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
<ToggleButton
Width="300" Height="300">
<ContentControl Content="{Binding ElementName=Bla, Path=MainContent}"/>
</ToggleButton>
</UserControl>
Dependency property:
public static DependencyProperty MainContentProperty = DependencyProperty.Register(
"MainContent",
typeof(object),
typeof(UserControl1),
null);
public object MainContent
{
get => GetValue(MainContentProperty);
set => SetValue(MainContentProperty, value);
}
The way I want to use the control:
<local:UserControl1>
<TextBlock>Whatever</TextBlock>
</local:UserControl1>
When I run the program, the textbox appears "Whatever", but the style is not applied, the ellipse won't show.
What's the correct way of doing this?
=== Update ===
OK, getting somewhere... finally...
Now I got this as user control:
<UserControl x:Class="WpfApp4.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"
xmlns:wpfApp4="clr-namespace:WpfApp4"
mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800"
Name="Bla">
<UserControl.Resources>
<Style TargetType="wpfApp4:UserControl1">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<ToggleButton>
<Grid>
<Ellipse Width="300" Height="300" Fill="Yellow"/>
<ContentPresenter Content="{Binding ElementName=Bla, Path=MainContent}" />
</Grid>
</ToggleButton>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
<ContentPresenter/>
</UserControl>
And this is how I use it:
<local:UserControl1>
<local:UserControl1.MainContent>
<TextBlock>Whatever</TextBlock>
</local:UserControl1.MainContent>
</local:UserControl1>
That finally gives me a toggle button with the style applied (the ellipse shows up) and the textbox is shown as well.
So, this works. Is this the way you mean it should work? Or can it be simplified?
It should be more like
<local:UserControl1>
<local:UserControl1.MainContent>
<TextBlock>Whatever</TextBlock>
</local:UserControl1.MainContent>
</local:UserControl1>
But you should look forward overriding ContentControl which would be more adequate rather then using UserControl.
By the way why did you put a ContentControl inside ToggleButton? ToggleButton by itself is a ContentControl it has it's own Content property.
Update:
All depends on what you whant to do. If it is only change the visual of the toggle button, then just create a toggle button style like this:
<ToggleButton>
<TextBlock>Whatever</TextBlock>
<ToggleButton.Style>
<Style TargetType="{x:Type ToggleButton}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ToggleButton}">
<Grid>
<Ellipse Width="300" Height="300" Fill="Yellow"/>
<ContentPresenter Content="{TemplateBinding Content}" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ToggleButton.Style>
</ToggleButton>
Ofcorse if you want to use your style across the application it is better to define the style in a resource dictionnary (for exemple in App.xaml), give it a key and call it on each toggle button using {StaticResource key}.
If on the other hand, you want to add some logic, you have to create a control class inheriting from ToggleButton and add the logic inside.

Add a mouseover and pressed/clicked effect on element in UWP app

I'm currently working on an app to show all parking spaces in Kortrijk (a city in Belgium). This is how it looks at the moment:
Design
My question is: how can I for example change the color of the element on mouseover or on click. I want to accomplish this in the XAML and this is the code that I have now.
Code
MainPage.xaml
<Page
x:Class="ParkingSpots.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:ParkingSpots"
xmlns:model="using:ParkingSpots.model"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:Maps="using:Windows.UI.Xaml.Controls.Maps"
mc:Ignorable="d">
<Page.Resources>
<model:ParkingSpot x:Key="spots"/>
</Page.Resources>
<Grid Style="{StaticResource mainGrid}">
<Grid.RowDefinitions>
<RowDefinition Height="20"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="Parking spots in Kortrijk"/>
<ListView ItemsSource="{Binding Source={StaticResource spots}, Path=ParkingSpots}" ItemTemplate="{StaticResource ParkingSpotTemplate}" ItemsPanel="{StaticResource ParkingSpotsTemplate}"/>
</Grid>
style.xaml (external xaml file)
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:ParkingSpots.style"
xmlns:conv="using:ParkingSpots.converter">
<conv:StreetConverter x:Key="StreetConv" />
<Color x:Key="Color1">#FFB3B6F2</Color>
<Color x:Key="Color2">#FF5A58D9</Color>
<Color x:Key="Color3">#FFF2F2F2</Color>
<SolidColorBrush x:Key="Color1Brush" Color="{StaticResource Color1}" />
<SolidColorBrush x:Key="Color2Brush" Color="{StaticResource Color2}" />
<SolidColorBrush x:Key="Color3Brush" Color="{StaticResource Color3}" />
<Style x:Name="mainGrid" TargetType="Grid">
<Setter Property="Background" Value="{StaticResource Color1Brush}"/>
</Style>
<DataTemplate x:Key="ParkingSpotTemplate">
<ListViewItem>
<ListViewItem.Resources>
<Style TargetType="TextBlock">
<Setter Property="Foreground" Value="{StaticResource Color3Brush}" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="Margin" Value="8,0,0,0" />
</Style>
</ListViewItem.Resources>
<TextBlock x:Name="ParkingSpotInfo" Grid.Row="0" Grid.Column="0" Text="{Binding Street, Converter={StaticResource StreetConv}}"/>
</ListViewItem>
</DataTemplate>
<ItemsPanelTemplate x:Key="ParkingSpotsTemplate">
<VariableSizedWrapGrid x:Name="wrapGrid"></VariableSizedWrapGrid>
</ItemsPanelTemplate>
I tried something with style.triggers but this is only possible in WPF apps and not in UWP apps. I've also read a lot of things about visualstates but I don't know how to use it and if this is the best way to do such effects.
Thanks in advance
You should probably be using a ListView to display this data instead of an ItemsControl (unless you have a good reason for doing so). ListView extends from ItemsControl and adds to it lots of useful features, such as:
Single/multiple item selection.
ItemClick event.
Each item container is a ListViewItem control which has its own features like visual states and a checkbox, and the presentation of the ListViewItem is managed by a ListViewItemPresenter which can deliver these features in an optimized manner.
Built-in ScrollViewer.
Data and UI virtualization. UI virtualization is a big advantage when you have 100s of items.
Accessible. Supports tab-focusing.
Probably more...
ItemsControl isn't typically used for situations where you want to interact with the items (by click/tap, for example).
ListView by default has its own style which can be easily overridden to match what you have already.
If you only want to style the ListViewItem background/foreground for each visual state, then you can override these styles:
<ListView>
<ListView.Resources>
<!--
These resources are applied to this ListView only. Put in a
higher scope (page or app) depending on what you want it to affect.
-->
<SolidColorBrush x:Key="ListViewItemBackgroundPointerOver" Color="Red"/>
<SolidColorBrush x:Key="ListViewItemForegroundPointerOver" Color="Violet"/>
<SolidColorBrush x:Key="ListViewItemBackgroundSelected" Color="Yellow"/>
<SolidColorBrush x:Key="ListViewItemForegroundSelected" Color="LimeGreen"/>
<SolidColorBrush x:Key="ListViewItemBackgroundSelectedPointerOver" Color="Blue"/>
<SolidColorBrush x:Key="ListViewItemBackgroundPressed" Color="Cyan"/>
<SolidColorBrush x:Key="ListViewItemBackgroundSelectedPressed" Color="Orange"/>
</ListView.Resources>
</ListView>

ContentControl does not show content in ControlTemplate

I need to create a custom control similar to WPF GroupBox. I started from the standard WPF Custom Control Library template in VS 2015 and defined my CustomGroupBox control like this:
public class CustomGroupBox : ContentControl
{
static CustomGroupBox()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomGroupBox), new FrameworkPropertyMetadata(typeof(CustomGroupBox)));
}
}
Then added the following minimal set of lines to develop my custom GroupBox according to the specification:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:CustomGroupBox">
<Style TargetType="{x:Type local:CustomGroupBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:CustomGroupBox}">
<Border Background="{TemplateBinding Background}"
BorderBrush="Gray" BorderThickness="3" CornerRadius="3">
<ContentControl />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
To debug and develop, I created a test form for my custom control:
<Window x:Class="CustomGroupBoxClient.MainWindow"
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:local="clr-namespace:CustomGroupBoxClient"
xmlns:ctrl="clr-namespace:CustomGroupBox;assembly=CustomGroupBox"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<ctrl:CustomGroupBox Margin="5">
<StackPanel Orientation="Vertical">
<TextBlock>Text Block #1</TextBlock>
<TextBlock>Text Block #2</TextBlock>
</StackPanel>
</ctrl:CustomGroupBox>
</Grid>
</Window>
However, when I launch this form, I see the border of my custom control but not the content (2 TextBlock's):
I've re-read many manuals and articles related to this topic, but still can't figure out why ContentControl in my ControlTemplate does not display the specified content. How to solve my problem?
Try using ContentPresenter instead of ContentControl in your template:
<ContentPresenter />
By default, ContentPresenter finds the Content property of its templated parent and displays whatever it finds there. You could change the name of the property it looks for by changing its ContentSource value...
<ContentPresenter ContentSource="FooBar" />
...but since you're inheriting from ContentControl, the default is probably what you want.

Rotating Controls within a StackPanel

I am trying to create my own expandable/collapsible menu (creatively called ExpandingMenu) in my first WPF project. I already have one user control consisting of a 2-row Grid (row 1 is a button to collapse and expand the control, row 2 is a StackPanel for rotating ToggleButtons, which is where I'm currently stuck). For my rotating buttons, I have just decided to make them their own UserControls as well.
The Buttons (I'm calling them ExpandingMenuButtons) are no more than a ToggleButton in a 1x1 Grid (I'm thinking of doing it this way since I may want to add some extra custom logic to these buttons after I get the standard behavior sorted out). I can add them to my menu control successfully, I can even get them to rotate via a RenderTransform on the Grid.
However, as you can probably tell, they swing up when they rotate. This causes them to not only be too high, but they also likely extend to far up.
This is what I currently have, before attempting rotations, it is behaving as expected.
This is what I'm trying to accomplish (edited using the magic of paint). I can get this correct behavior in my Menu control (tan areas), but I've mangled the expand/contract event in the meantime for testing purposes...
What I can do when I rotate 1 button (Like I mentioned earlier, I've mangled some behavior for my testing purposed, so each button is set to rotate on click, not all at once like you may expect). As you can see, this button has swung out from where it originally was. buttons higher up will swing partially/completely out of view. Instead, I would like them to rotate into the proper place. once I get one to work right, I assume it will be simple to get the rest behaving the same way, which I why I'm trying things this way..
My button code is below:
<UserControl x:Class="App.Controls.ExpandingMenuButton"
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:local="clr-namespace:App.Controls"
mc:Ignorable="d"
d:DesignHeight="30" d:DesignWidth="100">
<Grid Name="ButtonGrid" Height="30">
<ToggleButton Name="MenuButton" Background="Aqua" BorderThickness="0 0 0 1" Click="MenuButton_Click" Content="TEST"></ToggleButton>
</Grid>
</UserControl>
the only "real" code in ExpandingMenuButton.xaml.cs so far:
private void MenuButton_Click(object sender, RoutedEventArgs e)
{
//I know this is not practical, it is used for quick testing.
ButtonGrid.RenderTransform = new RotateTransform(-90);
}
My menu code so far:
<UserControl x:Class="App.Controls.ExpandingMenu"
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:local="clr-namespace:App.Controls"
mc:Ignorable="d"
Name="ucExpandingMenu"
MinWidth="32"
d:DesignHeight="300" d:DesignWidth="100">
<UserControl.Resources>
<SolidColorBrush x:Key="BackColor" Color="PeachPuff"/>
<!-- This style is used for buttons, to remove the WPF default 'animated' mouse over effect. http://stackoverflow.com/a/4182205/2957232 -->
<Style x:Key="ExpandButtonStyle" TargetType="Button">
<Setter Property="OverridesDefaultStyle" Value="True"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border Name="border"
BorderThickness="0 0 0 1"
BorderBrush="Black"
Background="{TemplateBinding Background}">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="border" Property="BorderBrush" Value="Black" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<StackPanel Name="MenuPanel" Width="100" HorizontalAlignment="Left" Background="{DynamicResource BackColor}" Grid.Row="1">
<!--Contents will go here-->
</StackPanel>
<Button Style="{StaticResource ExpandButtonStyle}" Width="100" Height="32" FontSize="18" VerticalAlignment="Center" HorizontalAlignment="Stretch" Panel.ZIndex="1" Background="{DynamicResource BackColor}" BorderThickness="0" Click="Button_Click" Content="ยป"></Button>
<Button Name="DummyFocus" Panel.ZIndex="0" Height="0" Width="0"></Button>
</Grid>
</UserControl>
And again, the only "real" code in this class so far:
private void Button_Click(object sender, RoutedEventArgs e)
{
MenuPanel.Children.Add(new ExpandingMenuButton("TEST ITEM"));
}
Please excuse my lack of WPF knowledge, I'm trying to come from a world of Winforms, where even there I have a lack of knowledge when it comes to this sort of thing. I know the code looks kinda funny, but hopefully the images show what I'm after here. So far I'm just testing this in a dummy window with only a grid and the HorizontalAlignment set to "Left". Nothing fancy.
Use LayoutTransform. Using RenderTransform does not affect how other controls (including parent) are being rendered/laid out; LayoutTransform does.
Shift the transform to the whole UserControl. You can probably specify directly in XAML instead.
Example:
<UserControl.LayoutTransform>
<RotateTransform Angle="-90" />
</UserControl.LayoutTransform>

WPF - Adding a ObjectDataProvider breaks my designer

Perhaps i'm not using the ObjectDataProvider correctly, but im following the MSDN examples so im not sure whats going wrong.
Goal: When i click a button, it will close the window by calling a method "exitButtonMethod" which simple does this.Close();.
<Window x:Class="WpfApplication1.Window1"
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"
xmlns:local="clr-namespace:WpfApplication1"
Title="Window1" WindowStyle="None" AllowsTransparency="True"
WindowStartupLocation="CenterScreen" mc:Ignorable="d" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" Height="254" Width="438" Opacity="1" Background="{x:Null}">
<Window.Resources>
<ObjectDataProvider ObjectType="{x:Type local:Window1}"
MethodName="exitButtonMethod" x:Key="callExitButtonMethod">
</ObjectDataProvider>
<Style x:Key="ExitButtons" TargetType="{x:Type Button}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate x:Name="exitButtonTemplate" TargetType="Button">
<Grid>
<Ellipse x:Name="exitButtonEllipse" Fill="#597E0000"/>
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsPressed" Value="True">
<Setter TargetName="exitButtonEllipse" Property="Fill" Value="#897E0000" />
<Binding Source="{StaticResource callExitButtonMethod}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid Width="400" Height="200" Opacity="1">
<Rectangle Height="200" HorizontalAlignment="Left" VerticalAlignment="Top" Name="rectangle1" Stroke="#FF7E0000" Width="400" RadiusX="40" RadiusY="40" Fill="#64860000" StrokeThickness="3" />
<Button Style="{StaticResource ExitButtons}" Content="X" Height="25" Width="25" Margin="359,16,16,0" VerticalAlignment="Top" Focusable="True" FontSize="15" FontFamily="PMingLiU" Foreground="#FF7E0000" Opacity="1"/>
</Grid>
The error is that it simply breaks my designer and gives me the following error in the designer:
System.Runtime.Remoting.RemotingException
[228940] Designer process terminated unexpectedly!
What the purpose of ObjectDataProvider is to create objects in XAML that you can bind to. You can also use it to bind to the results of calling a method on that object, which is what you are trying to do.
Here you are making an new object with the type of Window1 and binding to the method callExitButtonMethod. So you are unintentionally making a new window inside your window.
<ObjectDataProvider ObjectType="{x:Type local:Window1}"
MethodName="exitButtonMethod" x:Key="callExitButtonMethod">
</ObjectDataProvider>
Now that new window, when created, also has a window inside of it... etc. and you get an infinite loop of making windows.
This is why you are getting a stack overflow.
What you are trying to do is much simpler than what you are currently doing. In order to call a method on a button when you click it, you can simply do:
<Button Click="NameOfMethodHere" />
In your case, just add the Click parameter to your button and get rid of the ObjectDataProvider.
EDIT: Also to set events from a Style, see EventSetter.

Categories