I really like WPF because of its awesome skinning support by changing resourcedictionaries on the fly, but the catch is, the styles must be made by designers in XAML. My client needs a skinnable UI where the end users can make skins themselves. My question is -
In Photoshop, you can take any image, and add a color overlay to change all the colors to that hue. Can you do something similar in WPF? I'm just a beginner, and looking at several WPF styles, it seems like all the color values are hard-coded.
Here's a sample scenario - user selects two colors from color pickers, and all the controls have a gradient background from Color1 to Color2.
EDIT: Can the colors be saved to a XML file and loaded again too?
The key is to realize that a Style can contain a DynamicResource or a Binding, so if your style is:
<Style TargetType="{x:Type Button}">
<Setter Property="Background" Value="{DynamicResource UserSelectedBackground}" />
...
</Style>
anything you set as a "UserSelectedBackground" resource will be applied to all buttons.
Alternatively you might bind to a view model object:
<Style TargetType="{x:Type Button}">
<Setter Property="Background" Value="{Binding ButtonBackground, Source={x:Static my:SkinModel.Instance}" />
...
</Style>
Now whenever ButtonBackground in your SkinModel instance changes, all button backgrounds will automatically update. (This assumes your SkinModel uses DependencyProperties or implements INotifyPropertyChanged.)
To allow the user to separately control the two ends of a gradient fill, create two SolidColorBrush properties in your SkinModel which are bound from two-way by the color pickers. Whenever these properties change, recompute the ButtonBackground property (either in the PropertyChangedCallback of a DependencyProperty or in the setter of a CLR property).
Saving your state to the file is trivial: Just use XamlWriter to serialize your SkinModel to XAML, then write it to the file. To load it later, just use XamlReader.Parse.
You could store the color values in XML/DataBase (sqllite might be a good fit) and put them into a class that the controls will bind to. That way you can use a colorpicker for the user to change these data.
Related
I am tearing my hair out trying to create a Style for the "Button bar" buttons in an App I am working on, but I want the developer to be able to specify the colors used in Button gradient fills etc by adding xaml code in their Button declaration for the BackGround, Foreground and BorderBrush colors.
I have used the "Copy Template" trick of a totally undefined to get a copy of the full default Template for the control, but am totally confused by the way all the important colors are Hard coded internally in the style, being renamed with names such as "Button.Static.Background" which are all using HARD CODED COLORS attached to them eg:
<SolidColorBrush x:Key="Button.Pressed.Border" Color="#FF2C628B"/>
<SolidColorBrush x:Key="Button.Disabled.Background" Color="#FFF4F4F4"/>''
and these are then referenced later on in the template by these names. What I would really like to be able to do is to refer to the User defined properties for these items rather than hard coded values at this point in the template, but I cannot seem to find a way to do so.
As an example, here is what I am trying, but it doesn't work, although I do not get any errors as such. The first line is a standard declaration, the next 2 are the ones I want to allow the user to override when using this style to match their preferred background/foreground color schemes.
<SolidColorBrush x:Key="Button.Static.Border" Color="Black"/>
<SolidColorBrush x:Key="Button.MouseOver.Background" Color="{DynamicResource Background}"/>
<SolidColorBrush x:Key="Button.MouseOver.Border" Color="{DynamicResource Border}"/>
Later on in the template we come to :-
<Trigger Property="IsMouseOver" Value="true">
<Setter Property="Background"
TargetName="border"
Value="{StaticResource Button.MouseOver.Background}"/>
<Setter Property="BorderBrush"
TargetName="border"
Value="{StaticResource Button.MouseOver.Border}"/>
</Trigger>
'''
and this is where I want the user defined colors (if supplied) to be used when the mouseover occurs, rather than any hard coded values ?
.
Am I trying to be too clever here, or is there a sensible way I can achieve this ?
You can make a templated control based on nearly any control, this case being a button, and extend the control. You can add properties to allow users to specify the colors when using the control in the XAML. Of course, you’ll have to set default colors - likely being the colors you’ve used in your question.
Here’s an answer I wrote the other day on how to create a bindable control:
https://stackoverflow.com/a/66394791/11590704
The important part in the answer is the Dependency Properties and the Template property in the control’s Style. You don’t need to create a new control to use TemplateBinding.
You can use the same methods there and practically add any property you want. Foreground, MouseOverForeground, MouseOverBackground, etc.
An alternative would be to create a global theme. All your controls should use this theme (you’d likely have to create customized styles for each control to use the theme).
You would declare the default theme colors in a resource dictionary like you did in your question.
The benefit of a theme is that end users can customize the theme at run-time, e.g. change a button’s background color through the UI (you’d have to code in the theme adjustments in your .cs code). Also, your control will be visually coherent.
This is a larger task though but I’d keep it in mind when creating your controls. I’ve had end users who need high contrasting colors so I created a theme and some code for theme adjustments.
EDIT - More Information
You don't have to create dependency properties for the base properties (although I suppose you can if you really wanted to). You can work around them in either the XAML or the callbacks/events in your control's code-behind.
Example: Working around the base background property.
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
// Set the default background
DefaultBackground = this.Background;
}
// Store the default background color to revert back to
private Brush DefaultBackground;
// Dependency property for mouse over background color
public static readonly DependencyProperty MouseOverBackgroundProperty = DependencyProperty.Register(
nameof(MouseOverBackground), typeof(Brush),
typeof(NavButton), new PropertyMetadata(Brushes.Transparent));
public Brush MouseOverBackground
{
get => (Brush)GetValue(MouseOverBackgroundProperty);
set => SetValue(MouseOverBackgroundProperty, value);
}
// On Mouse Enter => Set Background
protected override void OnMouseEnter(MouseEventArgs e)
{
base.OnMouseEnter(e);
// Set background color
this.Background = MouseOverBackground;
}
// On Mouse Leave => Revert Background
protected override void OnMouseLeave(MouseEventArgs e)
{
base.OnMouseLeave(e);
// Set background color back
this.Background = DefaultBackground;
}
I used the events already in the control to handle the background color states. This is a basic example using overrides for the event handlers but you can subscribe to the events directly.
And then your XAML will look like this:
<local:NavButton Background="Transparent" MouseOverBackground="Red"/>
<!-- OR -->
<SolidColorBrush x:Key="ButtonBackground" Color="#1AFFFFFF" />
<SolidColorBrush x:Key="ButtonMouseOverBackground" Color="#00897B" />
<local:NavButton Background="{DynamicResource ButtonBackground}"
MouseOverBackground="{DynamicResource ButtonMouseOverBackground}"/>
The triggers are handled in the code-behind.
EDIT 2: Some more information
I forgot to mention that triggers may not work unless you have default values set. You set the default values in the Style.
<Style TargetType="{x:Type local:NavButton}">
<!-- The default value -->
<Setter Property="Background" Value="Grey" />
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<!-- The trigger value -->
<Setter Property="Backround" Value="Red" />
</Trigger>
</Style.Triggers>
</Style>
I have a WPF application with many different controls. I need to be able to set all child controls to be read only based on a property in my view model that I want to bind to.
There are a couple of challenges that I see:
How to ensure that setting the parent control to read only, also sets the child controls
Not all controls have a ReadOnly property - some IsReadOnly, some only have an IsEnabled
Has anyone any views on a generic solution rather than me having to bind the appropriate property (ReadOnly, IsReadOnly, etc) for each individual control?
Is there some way that I could use an attached property? Is there anyway, for example, that I could set a property on a grid, then in the code iterate through each child control setting it's appropriate property (if applicable at all)?
Any ideas welcomed.
David
I would recommend to do this using WPF implicit styles. The style would contain the Binding to the view model, for example:
<Style TargetType="{x:Type Button}">
<Setter Property="IsEnabled" Value="{Binding IsNotProcessing}" />
</Style>
As this style does not have the x:Key attribute set and uses the x:Type markup extension on the TargetType attribute, it is implicitly applied to all buttons in this case.
You would have to write an implicit style for each distinct control in your view as the following style would not be applied to all your buttons, text boxes and whatever controls you use (although the IsEnabled property is defined on FrameworkElement):
<!-- This implicit style is not applied as the x:Type must be the same type as
the targeted control; inheritance does not work here. -->
<Style TargetType="{x:Type FrameworkElement}">
<Setter Property="IsEnabled" Value="{Binding IsNotProcessing}" />
</Style>
Another option would be to make a single style that has a resource key an then reference this from every control, which is also quite cumbersome, but could be done relatively easy using Blend if you know all the controls at design time (you would select all controls and then apply the style using the properties window).
Hope this will help you.
Use the property IsHitTestVisible in xaml file to make real read only
<Grid IsHitTestVisible = "False">
//put a control
</Grid>
My intent is to override phone theme. By default page markup has following code:
<phone:PhoneApplicationPage
FontFamily="{StaticResource PhoneFontFamilyNormal}"
FontSize="{StaticResource PhoneFontSizeNormal}"
Foreground="{StaticResource PhoneForegroundBrush}"
I replace it with a style reference:
<phone:PhoneApplicationPage
Style="{StaticResource stylePage}"
Style is defined in app resources like this:
<Application.Resources>
<Style x:Key="stylePage" TargetType="phone:PhoneApplicationPage">
<Setter Property="Background" Value="White" />
<Setter Property="Foreground" Value="DeepSkyBlue" />
<Setter Property="FontFamily" Value="Arial" />
</Style>
</Application.Resources>
But background is still black, and font is white. Debugger shows that the values were applied.
If I set VisualRoot Grid's background, it's applied, but I want to set fontfamily and foreground values to be used by all my controls by default. Is it possible?
According to this msdn article - only the following properties are inherited in the visual tree in Silverlight: FontFamily, FontSize, FontStretch, FontStyle, FontWeight, and Foreground. Normally - dependency properties are not inherited in Silverlight (they often are in WPF). This is likely why these are not mentioned in the Silverlight MSDN article on dependency property value precedence, but the WPF version of the article mentions inherited value as lower priority than the local value or style setters. Your solution in fact works to some degree, but by default - most controls have font properties set by their style - Buttons by their default style, TextBlocks in page templates use PhoneTextNormalStyle or PhoneTextTitle1Style. If you remove the XML attributes setting the style of your TextBlocks - your application-wide font properties will apply to the TextBlocks.
Background dependency property unfortunately is not inherited, so your background will remain as is.
Your application-wide properties will not work on controls more complex than TextBlocks - like Buttons, since the default style of Buttons and other controls defines the style of TextBlocks explicitly using appropriate system styles.
I would say you should create your own resource dictionaries with styles of your controls and apply these to your controls manually, otherwise - you would need to write some code to automatically replace these properties at run time.
I have an application resource of the following
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Background" Value="{DynamicResource windowTextBackColor}"/>
<Setter Property="Foreground" Value="{DynamicResource windowsTextForeColor}"/>
</Style>
So all the text blocks in my application should assume those colours.
However the Menu and its containing MenuItems on my Main Window does not take these colours?
I have to do the XAML
for it to assume those colours, Is there a reason why setting a style that targets Text blocks does not work?
Thanks
I think you have to style the menu and menuitems separately. A MenuItem is a HeaderedContentControl, and its Header property is not a TextBlock, but an object, so it wouldn't be affected by a style for TextBlock.
You might also try changing that style to target Control instead of TextBlock. (Control is where Foreground and Background are defined.) I can't say for sure that it'll work, but if it does, it'll make every Control (TextBlocks, MenuItems, Buttons...) have those background and foreground colors.
Also, you might consider using BasedOn so that you can "inherit" the styles. If you don't, then styles defined farther up the hierarchy won't affect controls that have a style defined lower in the hierarchy. Basically, the lower ones mask the higher ones, unless you used BasedOn. Use it in this fashion:
BasedOn="{StaticResource {x:Type <your type here>}}"
Im currently creating a custom control (based on the WPF DataGrid). What i would like to do is to set default styling on the datagrid. Currently im setting the Style property which works. But my problem arrises when i create a style to change fx. the background color in the main applications app.xaml. Then all my "default" styling is lost and the DataGrid looks all standard only with the background property set.
I have tried using OverrideMetadata on each of the properties on the grid that i want to apply a default value to but with no luck. I also tried setting each property in the constructor but because of property precedence the styles from the main application then never is applied.
Did you set this in the static constructor?
DefaultStyleKeyProperty.OverrideMetadata(typeof(MyCustomType), new FrameworkPropertyMetadata(typeof(MyCustomType)));
Also, is the Key of your resource Style equal to your custom control's Type?
It mustn't have some other Key set, even with TargetType set to your control.
The Assembly should also be marked with the following attribute:
[assembly: ThemeInfo(
//where theme specific resource dictionaries are located
//(used if a resource is not found in the page,
// or application resource dictionaries)
ResourceDictionaryLocation.None,
//where the generic resource dictionary is located
//(used if a resource is not found in the page,
// app, or any theme specific resource dictionaries)
ResourceDictionaryLocation.SourceAssembly
)]
If you create a style without a dictionary key, it will style all objects of the type you specify within the scope that you import your style dictionary (if you specify it in a Window.Resources, it will have scope for that Window... if you specify it in App.xaml... you get the picture).
<Style TargetType="{x:Type Button}">
<Setter Property="FontFamily" Value="Times New Roman" />
<Setter Property="FontSize" Value="30" />
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="Background" Value="#FFCA5132" />
</Style>
This will give them all the same style.
This is a very powerful feature. It allows you to style any object, not just UI elements. You can style, say, one of your data entities like a "Person" object and when those elements are used visually, like databinding a list of type Person to a ListView, all of them will be styled how you specified, even though they are not natively UI elements. This is how WPF can be "lookless" about its controls.