hi im new in wpf I have a control that has been created in user control I want to create this control in style I do not know what to do:
this is xaml codes:
https://gist.github.com/ghost1372/8b3db759241b3ddb838789e446efb0b4#file-multiselectcombo-xaml
and this is cs codes:
https://gist.github.com/ghost1372/8b3db759241b3ddb838789e446efb0b4#file-multiselectcombo-cs
Well basically all you have to do is to define a Style and set the properties using Setters. Something like:
<Style x:Key="MultiSelectCombo" TargetType="{x:Type ComboBox}">
<Setter Property="SnapsToDevicePixels" Value="True"/>
...
<Setter Property="Template">
<Setter.Value>
<!-- Put the control template you used in your UserControl -->
</Setter.Value>
</Setter>
<Setter Property="ItemTemplate">
<Setter.Value>
<!-- Put the DataTemplate you used in your UserControl -->
</Setter.Value>
</Setter>
</Style>
Now this style can be applied to any ComboBox and will change its look. However if you want also change the behavior you need to create a MultiSelectCombo class that overrides ComboBox and implements the logic you want.
I am developing a custom DataGrid control that derives from the standard WPF DataGrid to extend it with some client-specific needs.
Snippet of my .cs file:
public class DataGrid : System.Windows.Controls.DataGrid
{
#region Constructor
static DataGrid()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(DataGrid),
new FrameworkPropertyMetadata(typeof(DataGrid)));
}
}
Snippet of my .xaml file:
<Style TargetType="local:DataGrid"
BasedOn="{StaticResource ResourceKey={x:Type DataGrid}}">
By not using the x:Key on the Style tag, I declare this Style as default one for the control. This works as I expect it.
What I do not get is how I correctly declare Default Styles for subcontrols which are used by the DataGrid, like DataGridRow, DataGridColumn or DataGridColumnHeader.
One way is to declare it like this...
<Style TargetType="{x:Type DataGridRow}">
... and set the CellStyle of my local:DataGrid explicitly (otherwise it still uses the standard WPF style for some reason(?))
<Setter Property="CellStyle" Value="{StaticResource {x:Type DataGridCell}}"/>
When clients use this custom DataGrid locally, they can modify the Style by simply referring to {x:Type DataGridCell}, which I find pretty elegant:
<local:DataGrid>
<lolcal:DataGrid.CellStyle>
<Style TargetType="DataGridCell" BasedOn="{StaticResource {x:Type DataGridCell}}">
The disadvantage of declaring it as Default Style is that when clients are using the standard WPF DataGrid, the Default Styles are overriden by my custom DataGrid.
The alternative way is to use the x:Key attribute when specifying my DataGridCell Style...
<Style x:Key="DataGridCellStyleKey" TargetType="{x:Type DataGridCell}">
... and set the CellStyle of my local:DataGrid to this Key
<Setter Property="CellStyle" Value="{StaticResource {x:Type DataGridCell}}" />
This way I don't override a Default Style, but when my clients want to locally modify the CellStyle, they need to reference the x:Key explicitely:
<local:DataGrid>
<local:DataGrid.CellStyle>
<Style TargetType="DataGridCell" BasedOn="{StaticResource DataGridCellStyleKey}">
I want to avoid them referencing the x:Key for simplicity and maintainability reasons.
What I am looking for is a way that combines the best of both solutions: Setting the Row/CellStyles as Default Styles to avoid the need to reference hard-coded x:Key strings, but also avoid overriding Default Styles of the standard WPF DataGrid. The only other solution I can think of is using custom controls for DataGridCell and other sub-controls too. Is this a viable solution?
I think you have to declare the style from your first approach in the scope of default style from your custom DataGrid as follows:
<Style TargetType="local:DataGrid" BasedOn="{StaticResource ResourceKey={x:Type DataGrid}}">
<Setter Property="CellStyle">
<Setter.Value>
<Style TargetType="DataGridCell">
<!-- Your Default Style here -->
</Style>
</Setter.Value>
</Setter>
</Style>
This will not override default DataGrids CellStyle and can been changed by user easily.
I've got a custom control in WPF, which has a variety of dependency properties that allow visual customization. For the sake of brevity I won't post the entire control, but it basically is setup like this:
<UserControl.Resources>
<Style TargetType="{x:Type MyControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type MyControl}">
<Border BorderBrush="{TemplateBinding BorderColor}">
// more stuff here
<ContentPresenter/>
</Border>
</ControlTemplate>
</Setter>
</Setter>
</Style>
</UserControl.Resources>
The BorderColor property works fine if I set it directly, like this:
<ctl:MyControl BorderColor="Brushes.Red">....</ctl:MyControl>
But I want to set it application-wide. The problem I have is if I simply set the style with no key, it does not apply. Like this:
<Window.Resources>
<Style TargetType="{x:Type ctl:MyControl}">
<Setter Property="BorderColor" Value="Brushes.Red"/>
</Style>
</Window.Resources>
This does not do anything to the control. So I thought I'd just set a key and apply that style, like this:
<Style TargetType="{x:type ctl:MyControl}" x:Key="myStyle">....</Style>
<ctl:MyControl Style="{StaticResource myStyle}">.....</ctl:MyControl>
But this causes the control to vanish, I'm assuming because it's removing the Template. What am I doing wrong? With other framework controls you can just set the properties you want without losing the control template.
You need to inherit from the default style you have created.
inherit style from default style
Lets say I want to set background of all StackPanel in my application to some color.
I got the following in my App.xaml:
<Style TargetType="StackPanel">
<Setter Property="Background" Value="#222222" />
</Style>
This will work only if the StackPanel is a pure StackPanel, and the StackPanel must be under the App. However, background color of subclass of StackPanel or StackPanel in a popup dialog will not be changed by this. For example:
public class MyStackPanel : StackPanel { ... }
One way to solve the subclassing problem is to extend UserControl, and embed the StackPanel into the UserControl. This is ok as long as you don't need access to properties of the StackPanel.
Any idea?
What is the best way to do WPF theming?
You can create an implicit style for your custom class that inherits from the base class's style
<Style TargetType="StackPanel">
<Setter Property="Background" Value="#222222" />
</Style>
<Style TargetType="{x:Type local:MyStackPanel}"
BasedOn="{StaticResource {x:Type StackPanel}}" />
It is not a good practice to override WPF controls. As you mentioned, you need to design a UserControl descendant for this.
You can bind properties of your StackPanel (inside your UserControl) to the properties of your UserControl descendant, which you could add in code or to the builtin properties of UserControl.
<UserControl DataContext="{Binding RelativeSource={RelativeSource Self}}">
<StackPanel Background="{Binding Path=Background}">
...
</StackPanel>
</UserControl>
Problem
I've created a custom control (OmniBox), which has its base style set with:
<Style x:Key="GridStyle" TargetType="Grid" BasedOn="{StaticResource BaseElement}">
<Setter Property="Margin" Value="0,2" />
</Style>
But when I'm using my control, I want to be able to do something like:
<UserControl.Resources>
<Style TargetType="{x:Type ui:OmniBox}">
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="Margin" Value="0,10"/> <!--Not Working?-->
</Style>
</UserControl.Resources>
<Grid>
<StackPanel>
<ui:OmniBox x:Name="One"... />
<ui:OmniBox x:Name="Two"... />
...
And have all instances of my control take on that default margin. Unfortunately, my controls are not responding to the style set in the resources. They are just keeping their default margin of "0,2".
Strangely, if I explicitly set the margin on my controls like so:
<ui:OmniBox x:Name="One" Margin="0,10" Style="OBDefaultStyle" ... />
<ui:OmniBox x:Name="Two" Margin="0,10" ... />
...
They DO use the margin of "0,10" rather than "0,2". How come the template type isn't working?
If it's relevant, my OmniBox control templates all look like this:
<Style TargetType="{x:Type local:OmniBox}" x:Key="OBDefaultStyle">
<Setter Property="Template" Value="{StaticResource OBDefaultTemplate}" />
</Style>
<ControlTemplate TargetType="{x:Type local:OmniBox}" x:Key="OBDefaultTemplate">
<Grid x:Name="PART_Grid" Style="{StaticResource GridStyle}">
... (Content)
</Grid>
</ControlTemplate>
First Attempt
In my grid style, I've tried setting Margin to
<Setter Property="Margin"
Value="{Binding Path=Margin, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:OmniBox}}}" />
But it didn't help in sucking down the templated margin.
Second Attempt
I tried creating a custom margin dependency property and binding the grid to that:
<Style x:Key="GridStyle" TargetType="Grid" BasedOn="{StaticResource BaseElement}">
<Setter Property="Margin" Value="{Binding Path=MyMargin, RelativeSource={RelativeSource TemplatedParent}}" />
</Style>
My custom property was defined as:
public static readonly DependencyProperty MarginProperty = DependencyProperty.Register("Margin", typeof(Thickness), typeof(OmniBox), new FrameworkPropertyMetadata(new Thickness(0,2,0,2), new PropertyChangedCallback(OnMarginChanged)));
Anyways it didn't work. The default margin set in the dependency property above is still overriding the margin I'm trying to set in the style template.
You can add a default style for a custom control by overriding the metadata for the DefaultStyleKey:
public class MyButton : Button
{
static MyButton()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MyButton), new FrameworkPropertyMetadata(typeof(MyButton)));
}
}
You then create a resource dictionary called Generic.xaml that is located in a directory called Themes in the root of the project (so the path will be "/Themes/Generic.xaml"). In that resource dictionary you create a default style for your control:
<!-- Base the style on the default style of the base class, if you don't want to completely
replace that style. If you do, remember to specify a new control template in your style as well -->
<Style TargetType="SomeNamespace:MyButton" BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="Margin" Value="10" />
</Style>
If you just add a MyButton control it will get the default style, but you can override properties set in the default style by applying a new style:
<Window x:Class="SomeNamespace.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:SomeNamespace="clr-namespace:SomeNamespace"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<Style TargetType="SomeNamespace:MyButton">
<Setter Property="Margin" Value="20" />
</Style>
</Window.Resources>
<Grid>
<SomeNamespace:MyButton />
</Grid>
</Window>
GridStyle specifies TargetType="Grid", so the setter <Setter Property="Margin" Value="0,2" /> applies to the Grid at the root of the control template. Setting the Margin property of the containing OmniBox has no effect of the margin of that grid.
Try specifying this in the template:
<Grid x:Name="PART_Grid" Margin="{TemplateBinding Margin}">
Notice I did not set the Style property as you did in the template. This is because the grid's Margin property will always reflect the Margin property of the OmniBox containing it, negating the effect of the Margin property in GridStyle. Instead you will want to default the OmniBox.Margin property and remove GridStyle entirely:
<Style TargetType="{x:Type local:OmniBox}" x:Key="OBDefaultStyle">
<Setter Property="Margin" Value="0 2" />
<Setter Property="Template" Value="{StaticResource OBDefaultTemplate}" />
</Style>
Have you overridden the DefaultStyleKey property in your OmniBox control?
After happening on this question, I figured out what I needed to do. In the control's class, I need to override the margin property's default value:
static OmniBox()
{
MarginProperty.OverrideMetadata(typeof(OmniBox), new FrameworkPropertyMetadata(new Thickness(0,2,0,2)));
}
After that, I get rid of the margin on the "Grid" component of the omnibox completely, since the control itself carries a margin. Now when the user sets the "Margin" property on the OmniBox, it accepts it, if they don't, it uses the default value.
Thank you all so much for your suggestions and effort.