Question

Is there a way to create a kind of "solidcolorbrush" that is a mixture of 2 solidcolor brushes?

For the back color, i would like to be able to use a DynamicReference to some other brush. While the other color (in the front) could be a static color with opacity.

Feel free to ask for clarification if this doesnt really make sence!

Was it helpful?

Solution

Unfortunately, custom brushes are not supported in WPF (the brush types are marked 'internal' and cannot be inherited from), so creating a brush that is a mixture of two brushes that can be used from XAML like a normal SolidColorBrush is not possible.

As a workaround, you could use a MarkupExtension to simulate the behaviour of a custom brush, which allows you to use XAML syntax and provide a custom value, which allows us to use the built-in SolidColorBrush (no custom brush necessary) set to the value you get when mixing two colors:

/// <summary>
/// Markup extension to mix two SolidColorBrushes together to produce a new SolidColorBrush.
/// </summary>
[MarkupExtensionReturnType(typeof(SolidColorBrush))]
public class MixedColorBrush : MarkupExtension, INotifyPropertyChanged
{
    /// <summary>
    /// The foreground mix color; defaults to white.  
    /// If not changed, the result will always be white.
    /// </summary>
    private SolidColorBrush foreground = Brushes.White;

    /// <summary>
    /// The background mix color; defaults to black.  
    /// If not set, the result will be the foreground color.
    /// </summary>
    private SolidColorBrush background = Brushes.Black;

    /// <summary>
    /// PropertyChanged event for WPF binding.
    /// </summary>
    public event PropertyChangedEventHandler PropertyChanged;

    /// <summary>
    /// Gets or sets the foreground mix color.
    /// </summary>
    public SolidColorBrush Foreground
    {
        get 
        { 
            return this.foreground; 
        }
        set 
        { 
            this.foreground = value; 
            this.NotifyPropertyChanged("Foreground"); 
        }
    }

    /// <summary>
    /// Gets or sets the background mix color.
    /// </summary>
    public SolidColorBrush Background
    {
        get 
        { 
            return this.background; 
        }
        set 
        { 
            this.background = value; 
            this.NotifyPropertyChanged("Background"); 
        }
    }

    /// <summary>
    /// Returns a SolidColorBrush that is set as the value of the 
    /// target property for this markup extension.
    /// </summary>
    /// <param name="serviceProvider">Object that can provide services for the markup extension.</param>
    /// <returns>The object value to set on the property where the extension is applied.</returns>
    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        if (this.foreground != null && this.background != null)
        {
            // Create a new brush as a composite of the old ones
            // This does simple non-perceptual additive color, e.g 
            // blue + red = magenta, but you can swap in a different
            // algorithm to do subtractive color (red + yellow = orange)
            return new SolidColorBrush(this.foreground.Color + this.background.Color);
        }

        // If either of the brushes was set to null, return an empty (white) brush.
        return new SolidColorBrush();
    }

    /// <summary>
    /// Raise the property changed event.
    /// </summary>
    /// <param name="propertyName">Name of the property which has changed.</param>
    protected void NotifyPropertyChanged(string propertyName)
    {
        if (this.PropertyChanged != null)
        {
            this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

Which can then be used from XAML as you would a normal brush:

<Grid>
    <Grid.Background>
        <local:MixedColorBrush Foreground="Blue" Background="Red"/>
    </Grid.Background>
</Grid>

Or by using the markup extension syntax:

<Grid Background="{local:MixedColorBrush Foreground=Blue, Background=Red}">

The downside to this approach is that you cannot use DynamicResource or StaticResource references to bind the values to other resources in your application. MarkupExtension is not a DependencyObject, and resource binding only works on DependencyObjects; the built-in Brushes are DependencyObjects, which is why binding works with traditional brushes.

OTHER TIPS

I ran into the same problem. I normally just use one xaml each for the basic darks, the basic lights, and then one for each colour accent (blue, red etc). The accent is slightly lookthrough which makes it darker when then darker theme is selected with a darker background.

When creating a theme with a secondary accent colour to have more contrast in the app (for example gray when light theme is selected, accent colour when dark theme), I needed to construct a brush out of two colours as otherwise I would have to create a dark and light theme for each colour.

Here is what I use:

<DrawingBrush x:Key="SecondaryAccentColorBrush" Viewport="0,0,1,1" TileMode="Tile">
    <DrawingBrush.Drawing>
        <DrawingGroup>
            <GeometryDrawing>
                <GeometryDrawing.Geometry>
                    <RectangleGeometry Rect="0,0,1,1" />
                </GeometryDrawing.Geometry>
                <GeometryDrawing.Brush>
                    <SolidColorBrush Color="{DynamicResource AccentColor}"/>
                </GeometryDrawing.Brush>
            </GeometryDrawing>
            <GeometryDrawing>
                <GeometryDrawing.Geometry>
                    <RectangleGeometry Rect="0,0,1,1" />
                </GeometryDrawing.Geometry>
                <GeometryDrawing.Brush>
                    <SolidColorBrush Color="{DynamicResource Gray10}"/>
                </GeometryDrawing.Brush>
            </GeometryDrawing>
        </DrawingGroup>
    </DrawingBrush.Drawing>
</DrawingBrush>

When the theme is switched, the alpha of "Gray10" switches between 00 and FF, thus the brush shows either gray or the accent colour.

Get the colors from the foreground and background brushes, mix them, and create a new brush from the resulting color.

Example in C#:

Color foreground = foregroundBrush.Color;
Color background = backgroundBrush.Color;

int opacity = 25;

int r = (opacity * (foreground.R - background.R) / 100) + background.R;
int g = (opacity * (foreground.G - background.G) / 100) + background.G;
int b = (opacity * (foreground.B - background.B) / 100) + background.B;

SolidColorBrush mixedBrush = new SolidColorBrush(Color.FromArgb(r, g, b));

One easy way to do it (but probably not optimized), create a LinearGradientBrush of the two colors in repeat mode with Endpoint equals to start point:

<LinearGradientBrush SpreadMethod="Repeat" EndPoint="0,0">
                                <GradientStop Color="Red" Offset="0" />
                                <GradientStop Color="Yellow" Offset="1" />
                            </LinearGradientBrush>

This one gives you an Orange brush.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top