How to build a custom component based on MaterialDesign? - wpf

I'm trying to understand how to build a custom component based on material design, in order to understand how exactly the procedure to achieve that is working I thought to build a simple button that includes text and an icon (remember is just for exercise), so I tried to write both a UserControl and a ResurceDictionary, but so far no luck. My question is, how can I build a custom button based on material design? I want it to maintain all effects and shadows that are shipped with material design. I'll post also what I have in terms of ResurceDictionary.
ResourceDictionary
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:local="clr-namespace:KESS3Mockup">
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.Light.xaml" />
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.Defaults.xaml" />
</ResourceDictionary.MergedDictionaries>
<Style TargetType="{x:Type local:VerticalButton}" BasedOn="{StaticResource ResourceKey={x:Type Button}}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:VerticalButton}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
CornerRadius="2"
BorderThickness="{TemplateBinding BorderThickness}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="0.5*"/>
<RowDefinition Height="8*"/>
<RowDefinition Height="8*"/>
<RowDefinition Height="0.5*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="20*"/>
<ColumnDefinition Width="1*"/>
</Grid.ColumnDefinitions>
<Viewbox HorizontalAlignment="Stretch" Grid.Column="1" Grid.Row="1">
<materialDesign:PackIcon Kind="{TemplateBinding Kind}" Foreground="White" />
</Viewbox>
<Viewbox Grid.Column="1" Grid.Row="2">
<TextBox Text="{TemplateBinding Text}" Foreground="White" SelectionBrush="#000078D7" BorderBrush="#00000000" Focusable="False"/>
</Viewbox>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>

You can base a new style on an existing style using BasedOn.
<Style TargetType="{x:Type local:VerticalButton}" BasedOn="{StaticResource {x:Type Button}}">
Instead of the type, you can also refer to a different, specific style.
<Style TargetType="{x:Type local:VerticalButton}" BasedOn="{StaticResource MaterialDesignFlatButton}"/>
However, for this to work, the target types must be compatible (either the same type or a base type, e.g. ButtonBase for a Button). You cannot simply create a UserControl and create a style based on a style for a Button. In order to customize a Button, you would have to create a custom control VerticalButton that inherits from Button, which implements the dependency properties and specifics you want.
public class VerticalButton : Button
{
static VerticalButton()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(VerticalButton), new FrameworkPropertyMetadata(typeof(VerticalButton)));
}
// ...your custom code.
}
Next you would create a Generic.xaml file in the Themes folder in your project (this path and file are named by convention). There you define the default style, which can be based on Material Design.
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:YourProject">
<Style TargetType="{x:Type local:VerticalButton}" BasedOn="{StaticResource {x:Type Button}}">
<!-- ...your style definitions. -->
</Style>
</ResourceDictionary>
This resource dicionary has to be included after the Material Design theme dictionaries in App.xaml. For more information on how to override default Material Design themes, you can refer to the Wiki.
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.Light.xaml" />
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.Defaults.xaml" />
<ResourceDictionary Source="Themes/Generic.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
Please not that although you can base a style on another style, you cannot base one control template on another. That means, if you assign a ControlTemplate in your style it will override the one of the base style. Therefore, if you want to adapt the control template while preserving most of its default visuals and effects, you have to copy it from the Material Design GitHub repository and adapt it to your needs.
For more information on developing custom controls in general see Control authoring overview. Material Design uses the established concepts for themes, styles and templates, so it does not really differ from styling standard WPF controls or building custom controls.
For simple cases of customizing the content of the button, it might be an easier alternative to create a DataTemplate that you assign as a ContentTemplate of the Button, see Data Templating Overview.

When you set the control template, it overrides all the visual styles. It merely carries only the dependency properties. So, it will not carry any shadow or other effects (at least to your new style). however, if such property exists in the VerticalButton class (that you are targeting), then you can reuse such properties and define your own style.

Related

Custom Window Style not showing in design view

I am attempting to customize the window style in a WPF application in VS2019 (.NET Core 3.1). I'm following along with a video, currently adding the style directly to the MainWindow.xaml. None of my style shows up in the XAML design view (in the video it does), however, the style shows correctly at runtime.
All other styles show up fine. I dug around looking for perhaps a window style I had set somewhere else but I'm not finding anything. At first, I was having an error on the inner <WindowChrome.../> saying "object of type 'Microsoft.VisualStudio.XSurface.Wpf.Window' cannot be converted to type 'System.Windows.Window'" but that issue appears to have resolved itself. I'm not sure if that is related or a clue.
Any idea what is going on and how to fix it? Or perhaps a hack to force a design-time style to take so I can see what I'm doing?
Code:
<Window x:Class="FirmwareUpdater.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:FirmwareUpdater"
xmlns:vm="clr-namespace:FirmwareUpdater.ViewModels"
mc:Ignorable="d"
WindowStyle="None"
WindowStartupLocation="CenterScreen"
x:Name="AppWindow"
Title="Firmware Updater"
Height="600" Width="800">
<Window.Resources>
<Style TargetType="{x:Type local:MainWindow}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Window}">
<Border Background="Gray" Padding="{Binding OuterMarginSize, FallbackValue=10}">
<Grid Background="Red">
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<WindowChrome.WindowChrome>
<WindowChrome
CaptionHeight="{Binding TitleHeight}"
ResizeBorderThickness="0"
GlassFrameThickness="0"
CornerRadius="0"/>
</WindowChrome.WindowChrome>
<Grid>
<!-- Displays pages for navigation-->
<Frame x:Name="mainFrame"
MaxHeight="600" MaxWidth="800" Margin="0,0,0,0"
NavigationUIVisibility="Hidden" />
</Grid>
</Window>
Recently, I was following this tutorial series and got this problem too, no matter which designer I use: NetFramework or NetCore.
For me, the only way to see changes during design-time is to define the ControlTemplate with a key in Window.Resources and set Window's Template as DynamicResource:
<Window ...
Template="{DynamicResource WindowBase}">
<Window.Resources>
<ControlTemplate x:Key="WindowBase" TargetType="{x:Type Window}">
</ControlTemplate>
</Window.Resources>
</Window>
I don't know how much this solution is reliable, but at least I don't have to run my app to see changes.
EDIT
You can also move the ControlTemplate from Window.Resources to some ResourceDictionary and set it as StaticResource for your Window's template.

My Two DataGrid XAML Resources are Mutually Exclusive --- Why?

In order to have a nicer look, I am trying to add to my DataGrid 2 features that I found in separate places, but for some reason they just can't get along.
I can either have the section inside the blue rectangle or the one in the red one.
TIA
Put the <ControlTemplate> tag inside the <ResourceDictionary> tag, underneath the closing </ResourceDictionary.MergedDictionaries> tag.
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Themes\DataGrid.Generic.xaml" />
</ResourceDictionary.MergedDictionaries>
<ControlTemplate x:Key="SelectAllButtonTemplate" TargetType="{x:Type Button}">
<Grid>
<Rectangle x:Name="Border" Fill="LightBlue" SnapsToDevicePixels="True" />
</Grid>
</ControlTemplate>
</ResourceDictionary>
</UserControl.Resources>

Using a generic style as base vs custom user control

I'm creating a resource dictionary to my application, where I'll have some "icon+text" buttons. Since they will all look the same (except for the icon and the text), I've created a generic style to serve as base to the others:
<!-- Generic ActionButtonStyle -->
<Style x:Key="ActionButtonStyle" TargetType="{x:Type Button}">
<!-- some setter properties -->
<Setter Property="ContentTemplate" Value="{DynamicResource ButtonDataTemplate}"/>
</Style>
<DataTemplate x:Key="ButtonDataTemplate">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="24" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Image Source="{Binding Source}"
Stretch="Uniform"
Grid.Column="0"
Margin="2"/>
<TextBlock Text="{Binding text}"
TextWrapping="Wrap"
Grid.Column="1"
Margin="2"
VerticalAlignment="Center"/>
</Grid>
</DataTemplate>
And I have some images for the icons:
<!-- Icons -->
<ImageSource x:Key="textToSpeech">Images/GreyIcons/TextToSpeech.png</ImageSource>
<ImageSource x:Key="play">Images/GreyIcons/Play.png</ImageSource>
<ImageSource x:Key="playSound">Images/GreyIcons/PaySound.png</ImageSource>
.
.
.
.
<ImageSource x:Key="group">Images/GreyIcons/Goup1.png</ImageSource>
And I'd like to create individual styles for each button (corresponding to each icon). Something like this:
<!-- Specific ActionButtonStyles -->
<Style x:Key="TextToSpeechButtonStyle" TargetType="{x:Type Button}" BasedOn="{StaticResource ActionButtonStyle}">
<Setter Property="Content">
<Setter.Value>
<Image Source="{StaticResource textToSpeech}"
</Setter.Value>
</Setter>
</Style>
I know that this doesn't work.. How should I do it? Should I create a custom user control for the generic button? The text will be binding to an object in my Model, and so will the command (to an action).
The example of what you are looking for seems to be missing, but it seems that you may be looking for "BasedOn" - which allows you to inherit, but still override a previously defined style. You can implement it like this:
<Style x:Key="MyButtonStyle" BasedOn="{StaticResource ActionButtonStyle}">
<Setter.../>
</Style>
You need to create a derived class from Button that adds two new DependancyProperties. They would be called something like Text and ImageSource. Your derived class would also set the ContentTemplate as you have indicated. This ContentTemplate would bind against the Text and ImageSource dependancy properties.
You can then create your custom control in XAML like this...
<app:CustomButton Text="Play" Source="{Binding play}"/>
...but if you want the same button over and over again you could create a style that is applied to the CustomButton and sets those two properties as required.

Howto: Applying a Style defined in generic.xaml to a UserControl? (WPF)

I've created a style in generic.xaml that i want to use in my project on several UserControls. In the same way i have defined a style for a custom control and this one works so it seems generic.xaml is loaded, this is the defined style:
<Style TargetType="{x:Type UserControl}" x:Key="ServiceStyle" x:Name="ServiceStyle">
<Setter Property="Opacity" Value="0.5"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type UserControl}">
<Border Name="border" CornerRadius="20"
Margin="10"
BorderThickness="5"
BorderBrush="Black">
<ContentPresenter Content="{TemplateBinding Content}"
Margin="{TemplateBinding Padding}"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
But now i want to use this style but i cannot get it to work. i've tryed adding it as a style parameter to a custom instance of UserControl in the following way:
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Netcarity"
xmlns:CustomControls="clr-namespace:Netcarity.CustomControls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" x:Name="Portier_deur" x:Class="Netcarity.UserControlPortier"
Height="600" Width="800" MouseDown="UserControl_MouseDown" Loaded="UserControl_Loaded" mc:Ignorable="d"
Style="{StaticResource ServiceStyle}">
however this gives me a note that the resource ServiceStyle could not be found. When trying to run there is a runtime error on this.
Thanks in advance.
It seems that Generic.xaml is not the right place to store styles for non custom controls. Somewhere i found the hint to put the syle in App.xaml instead of generic.xaml and this worked directly. So it seams Generic.xaml can only be used to store styles for customControls.
Maybe someone can add a more educated reason for this behaviour?
When Generic.xaml use ComponentResourceKey.

WPF Custom Control Template Not Applied

I'm sure this question or derivatives of it have been asked a bazillion times, but I couldn't find anything that helped me solve the problem, so I'm asking. Please feel free to direct me to the duplicate that I'm sure exists but I can't find. Apparently I'm not so great with keywords.
I have a Custom Control, it has it's own Resource Dictionary used only to define the control template. This dictionary is then merged into Generic.xaml.
The problem is that when this control shows up in the UI, it has nothing inside of it. I used Snoop to find this out. The control is in the UI, but it is completely empty.
Below you'll find the items that I think are responsible for the problem. Any help or advice you can offer is greatly appreciated.
The relevant parts of my folder structure are like this:
BasicTemplate.xaml:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WPFSpecBuilder.Layouts.Templates">
<Style TargetType="{x:Type local:BasicTemplate}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:BasicTemplate}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Grid>
<TextBlock Text="This is a basic template." />
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
Generic.xaml
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Layouts/Templates/XAML/BasicTemplate.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
Try this:
Set Build Action to BasicTemplate.xaml to Page.
Add reference to BasicTemplate.xaml in Generic.xaml:
ResourceDictionary Source="/WPDSpecBuilder;component/Layouts/Templates/Xaml/BasicTemplate.xaml"
It should works.
I think this may be as simple as changing the relative path of the merged dictionary. Try adding a / to the start of the folder path:
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/Layouts/Templates/XAML/BasicTemplate.xaml" />
</ResourceDictionary.MergedDictionaries>
Try:
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/WPDSpecBuilder;component/Layouts/Templates/XAML/BasicTemplate.xaml" />
</ResourceDictionary.MergedDictionaries>
See here for more details on Pack Uri's

Resources