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.
Related
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....
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.
I've create a custom control which inherit from WPF TextBox.
My Control Template simply add a small button on textbox in order to delete its text quicker.
However, I've noticed that when my textBox got the focus, its border doesn't change (blue color) as is the case for classic textbox.
I would preserver all aspect of original textBox, just like the border when the control get focus.
Am I missing something?
##EDIT
<TextBox x:Class="XTextBox.WKTextBox"
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"
Height="23" Width="200"
>
<TextBox.Resources>
<ControlTemplate x:Key="IconButton" TargetType="{x:Type ToggleButton}">
<Border>
<ContentPresenter />
</Border>
</ControlTemplate>
</TextBox.Resources>
<TextBox.Style>
<Style TargetType="TextBox">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TextBox}">
<Grid>
<Border BorderThickness="1" BorderBrush="DarkGray">
<ScrollViewer x:Name="PART_ContentHost" />
</Border>
<ToggleButton Template="{StaticResource IconButton}"
MaxHeight="21"
Margin="-1,0,0,0"
Name="imgButton"
Focusable="False"
IsChecked="False">
<Image Name="imgClearText" Source="Images\x.png" Stretch="Uniform" Opacity="0.5" Visibility="Visible" HorizontalAlignment="Right" >
</Image>
</ToggleButton>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</TextBox.Style>
Unfortunately, you can't simply Replace part of default template in WPF without loosing functionallity.
I believe the easiest solution would be to donwload Blend (it comes with VS2015). Open it, create an emty textbox and edit its template:
Blend will make a copy of default template, so you won't loose any of your default behaviour, like selection, focus etc.
Then you can save a project, open it in VS and refactor it as you want. Like moving style to dictionary or something.
You can manually get the same effect by adding handlers for the GotFocus & LostFocus events of the Border and set the highlight colors you want there.
<Border BorderThickness="1" BorderBrush="DarkGray" LostFocus="Border_LostFocus" GotFocus="Border_GotFocus">
and in your .cs file
private void Border_LostFocus(object sender, RoutedEventArgs e)
{
((Border)sender).BorderBrush = new SolidColorBrush(Colors.DarkGray);
}
private void Border_GotFocus(object sender, RoutedEventArgs e)
{
((Border)sender).BorderBrush = new SolidColorBrush(Colors.LightBlue);
}
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.
I just created a DependencyProperty for my custom button.
The property is called IsChecked.
If IsChecked == true my button shall change its background color to something else and keep this color until IsChecked == false.
Here is my DependencyProperty:
public static readonly DependencyProperty IsCheckedProperty = DependencyProperty.Register("IsChecked", typeof(bool), typeof(MainMenuButton), new PropertyMetadata(false));
public bool IsChecked
{
get { return (bool)GetValue(IsCheckedProperty); }
set { SetValue(IsCheckedProperty, value); }
}
Next I have a ControlTemplate for this button:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ControlTemplate x:Key="MainMenuButtonStyle" TargetType="{x:Type UserControl}">
<ControlTemplate.Resources>
/* Some storyboards */
</ControlTemplate.Resources>
<Grid x:Name="grid">
<Grid.Background>
<SolidColorBrush Color="{DynamicResource MainUI_MainMenuButtonBackground}"/>
</Grid.Background>
</Grid>
<ControlTemplate.Triggers>
/* Some triggers */
</ControlTemplate.Triggers>
</ControlTemplate>
My problem is now how I can access IsChecked and based on the current value of it change the background of grid? Haven't done that before only tried it some time ago and totally failed.
Thanks in advance :)
Edit:
Here is the UserControl aswell:
<UserControl
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"
mc:Ignorable="d"
x:Class="IFCSMainInterface.MainMenuButton"
x:Name="UserControl"
d:DesignWidth="640" d:DesignHeight="480" Width="272" Height="110" Template="{DynamicResource MainMenuButtonStyle}">
<Grid x:Name="LayoutRoot"/>
I think the problem is here:
<ControlTemplate x:Key="MainMenuButtonStyle" TargetType="{x:Type UserControl}">
You must set the correct type in order to use its dependency property.
Write in your ResourceDictionary the namespace of the class, and put your type correctly in control template, eg:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:my="clr-namespace:ProjectName.NameSpace">
<ControlTemplate TargetType="my:MainMenuButton" x:Key="MainMenuButtonStyle">
<!-- ... -->
</ControlTemplate>
</ResourceDictionary>
Edit (now that was explained):
You're trying to define a template in xaml own control
That does not seem quite right to do, I suggest looking for other approaches, such as creating a control that uses a Generic.xaml
( http://utahdnug.org/blogs/xamlcoder/archive/2007/12/13/building-custom-template-able-wpf-controls.aspx )
but if you want to use the dependency properties (the way you're doing) you can try the following code (for example):
<UserControl x:Class="SamplesStack.Controls.MyUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:my="clr-namespace:SamplesStack.Controls">
<UserControl.Template>
<ControlTemplate TargetType="UserControl">
<Grid x:Name="LayoutRoot">
<ContentPresenter />
</Grid>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=Self}, Path=IsChecked}" Value="True">
<Setter TargetName="LayoutRoot" Property="Background" Value="Red"/>
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</UserControl.Template>
This will make your control work as expected. Though not think very cool use DataTrigger.