WPF User Control / Control Template

I am creating a wpf application with a custom control and everything is working so far. But now I am facing two problems:

  • I want to assign a background color to my control, but this overlays the rectangle inside the grid, so the rectangle becomes invisible.
  • I tried to write a template for ContentControl, but the content will not display as expected, which means that only the display name is displayed with the text of each progress bar.

Template for my custom control (if interesting code will add it too):

<Style TargetType="{x:Type local:MetroProgressBar}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type local:MetroProgressBar}">
                <Grid Background="{TemplateBinding Background}">
                    <Rectangle Fill="{TemplateBinding ProgressBrush}" HorizontalAlignment="Left"
                               VerticalAlignment="Stretch" Width="{TemplateBinding ProgressBarWidth}"
                               Visibility="{TemplateBinding IsHorizontal, Converter={StaticResource BoolToVis}}"/>

                    <Rectangle Fill="{TemplateBinding ProgressBrush}" HorizontalAlignment="Stretch"
                               VerticalAlignment="Bottom" Height="{TemplateBinding ProgressBarHeight}"
                               Visibility="{TemplateBinding IsVertical, Converter={StaticResource BoolToVis}}"/>

                    <Border
                        Background="{TemplateBinding Background}"
                        BorderBrush="{TemplateBinding BorderBrush}"
                        BorderThickness="{TemplateBinding BorderThickness}"/>

                    <TextBlock VerticalAlignment="Center" HorizontalAlignment="Center"
                               Text="{TemplateBinding Text}"
                               FontSize="{TemplateBinding FontSize}" FontStyle="{TemplateBinding FontStyle}" FontWeight="{TemplateBinding FontWeight}"
                               FontFamily="{TemplateBinding FontFamily}" FontStretch="{TemplateBinding FontStretch}"
                               Foreground="{TemplateBinding Foreground}" TextWrapping="Wrap"/>

                    <Polygon Fill="{TemplateBinding BorderBrush}" Points="{TemplateBinding LeftBorderTriangle}"/>
                    <Polygon Fill="{TemplateBinding BorderBrush}" Points="{TemplateBinding RightBorderTriangle}"/>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>  

      

Template for ContentControl:

<vm:RamViewModel x:Key="RamInformationSource"/>

<Style TargetType="ContentControl" x:Key="MemoryUsageTemplate">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate>
                <Grid DataContext="{Binding Source={StaticResource RamInformationSource}}">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="*"/>
                        <ColumnDefinition Width="*"/>
                        <ColumnDefinition Width="*"/>
                        <ColumnDefinition Width="*"/>
                    </Grid.ColumnDefinitions>

                    <TextBlock HorizontalAlignment="Center" Text="{Binding DisplayName}" VerticalAlignment="Center"
                               FontSize="15"/>

                    <ctrl:MetroProgressBar Grid.Column="1" VerticalAlignment="Stretch" Width="55" Orientation="Vertical" HorizontalAlignment="Center"
                                           ExtenedBorderWidth="0.25" BorderBrush="Gray" Text="Available memory" Progress="{Binding AvailableMemory}"
                                           MaxValue="{Binding TotalMemory}"/>

                    <ctrl:MetroProgressBar Grid.Column="2" VerticalAlignment="Stretch" Width="60" Orientation="Vertical" HorizontalAlignment="Center"
                                           ExtenedBorderWidth="0.2" BorderBrush="Black" Text="Total memory" Progress="100"
                                           MaxValue="{Binding TotalMemory}"/>

                    <ctrl:MetroProgressBar Grid.Column="3" VerticalAlignment="Stretch" Width="60" Orientation="Vertical" HorizontalAlignment="Center"
                                           ExtenedBorderWidth="0.2" BorderBrush="DodgerBlue" Text="Used memory" Progress="{Binding UsedMemory}"
                                           MaxValue="{Binding TotalMemory}"/>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

      

xaml displaying content:

...
<ContentControl Style="{StaticResource MemoryUsageTemplate}"/>

<ctrl:MetroProgressBar Grid.Row="1" BorderBrush="Black" Text="Test" HorizontalAlignment="Left" Background="Aquamarine"
                       Orientation="Horizontal" BorderThickness="2" Height="50" Width="200" Progress="46"/>

<ctrl:MetroProgressBar Grid.Row="1" BorderBrush="Black" Text="Test" HorizontalAlignment="Right"
                       Orientation="Horizontal" BorderThickness="2" Height="50" Width="200" Progress="46"/>
...  

      

Top: ContentControl with the template applied, left bottom: custom control with background set, right bottom: custom control with no background set

The top of the image shows a content control with a template applied. The bottom shows two progress bars as defined in the last xaml (left in background, right without). These are all custom DPs that are defined for the control:

/// <summary>
/// Identifies the ExtenedBorderWidth property.
/// </summary>
public static readonly DependencyProperty ExtenedBorderWidthProperty =
    DependencyProperty.Register("ExtenedBorderWidth", typeof(double), typeof(MetroProgressBar),
                                    new PropertyMetadata(0.17, ExtendedBorderWidthValueChanged));

/// <summary>
/// Identifies the Text property.
/// </summary>
public static readonly DependencyProperty TextProperty =
    DependencyProperty.Register("Text", typeof(string), typeof(MetroProgressBar), new PropertyMetadata(""));

/// <summary>
/// Identifies the Orientation property.
/// </summary>
public static readonly DependencyProperty OrientationProperty =
    DependencyProperty.Register("Orientation", typeof(Orientation), typeof(MetroProgressBar), new PropertyMetadata(Orientation.Horizontal, OrientationValueChanged));

/// <summary>
/// Identifies the IsHorizontal property.
/// </summary>
public static readonly DependencyProperty IsHorizontalProperty =
    DependencyProperty.Register("IsHorizontal", typeof(bool), typeof(MetroProgressBar), new PropertyMetadata(true, OrientationChangedByProperty));

/// <summary>
/// Identifies the IsVertical property.
/// </summary>
public static readonly DependencyProperty IsVerticalProperty =
    DependencyProperty.Register("IsVertical", typeof(bool), typeof(MetroProgressBar), new PropertyMetadata(false, OrientationChangedByProperty));

/// <summary>
/// Identifies the ProgressBrush property.
/// </summary>
public static readonly DependencyProperty ProgressBrushProperty =
    DependencyProperty.Register("ProgressBrush", typeof(Brush), typeof(MetroProgressBar), new PropertyMetadata(new SolidColorBrush(Colors.LightGreen)));

/// <summary>
/// Identifies the LeftBorderTriangle property.
/// </summary>
public static readonly DependencyProperty LeftBorderTriangleProperty =
    DependencyProperty.Register("LeftBorderTriangle", typeof(PointCollection), typeof(MetroProgressBar), new PropertyMetadata(new PointCollection()));

/// <summary>
/// Identifies the RightBorderTriangle property.
/// </summary>
public static readonly DependencyProperty RightBorderTriangleProperty =
    DependencyProperty.Register("RightBorderTriangle", typeof(PointCollection), typeof(MetroProgressBar), new PropertyMetadata(new PointCollection()));

/// <summary>
/// Identifies the MaxValue property.
/// </summary>
public static readonly DependencyProperty MaxValueProperty =
    DependencyProperty.Register("MaxValue", typeof(ulong), typeof(MetroProgressBar), new PropertyMetadata(100UL, MaxValueChanged));

/// <summary>
/// Identifies the Progress property.
/// </summary>
public static readonly DependencyProperty ProgressProperty =
    DependencyProperty.Register("Progress", typeof(double), typeof(MetroProgressBar), new PropertyMetadata(0.0d, ProgressValueChanged));

/// <summary>
/// Identifies the ProgressBarWidth property.
/// </summary>
public static readonly DependencyProperty ProgressBarWidthProperty
    = DependencyProperty.Register("ProgressBarWidth", typeof(double), typeof(MetroProgressBar), new PropertyMetadata(0.0d));

/// <summary>
/// Identifies the ProgressBarHeight property.
/// </summary>
public static readonly DependencyProperty ProgressBarHeightProperty
    = DependencyProperty.Register("ProgressBarHeight", typeof(double), typeof(MetroProgressBar), new PropertyMetadata(0.0d));

      

DP value changed callbacks and instance methods:

#region Static

/// <summary>
/// Changes the orientation based on the calling property.
/// </summary>
/// <param name="source">The source of the event.</param>
/// <param name="e">Event information.</param>
private static void OrientationChangedByProperty(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
    //lock (lockOrientationByProperty)
    {
        MetroProgressBar pb = source as MetroProgressBar;

        if (e.Property == IsVerticalProperty)
        {
            if ((bool)e.NewValue)
            {
                pb.IsHorizontal = false;
                pb.Orientation = Orientation.Vertical;
            }
            else
            {
                pb.IsHorizontal = true;
                pb.Orientation = Orientation.Horizontal;
            }
        }
        else
        {
            // IsVerticalProperty is property that changed
            if (!(bool)e.NewValue)
            {
                pb.IsHorizontal = false;
                pb.Orientation = Orientation.Vertical;
            }
            else
            {
                pb.IsHorizontal = true;
                pb.Orientation = Orientation.Horizontal;
            }
        }

        AdjustVisibleProgressRect(pb);
    }
}

/// <summary>
/// Sets the progress value to the new maximum value, if the new max value is less than
/// the current progress value.
/// </summary>
/// <param name="source">The source of the event.</param>
/// <param name="e">Event information.</param>
private static void MaxValueChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
    //lock (lockMaxValue)
    {
        MetroProgressBar pb = source as MetroProgressBar;

        ulong val = Convert.ToUInt64(e.NewValue);
        if (val < Convert.ToUInt64(pb.Progress))
        {
            pb.Progress = val;

            // Raise finished event
            pb.OnFinished(EventArgs.Empty);
        }
    }
}

/// <summary>
/// Changes the width of the progress indication rectangle of the progress bar.
/// </summary>
/// <param name="source">The source of the event.</param>
/// <param name="e">Event information.</param>
private static void ProgressValueChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
    //lock (lockProgress)
    {
        MetroProgressBar pb = source as MetroProgressBar;
        AdjustVisibleProgressRect(pb, (double)e.NewValue);

        pb.OnProgressChanged(new ProgressChangedEventArgs((double)e.NewValue));

        // If new progress value equals or is greater than max value raise the finished event
        if (pb.MaxValue <= Convert.ToUInt64(e.NewValue))
            pb.OnFinished(EventArgs.Empty);
    }
}

/// <summary>
/// Changes the width of the progress indication rectangle of the progress bar.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">Event information.</param>
private static void OrientationValueChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
    //lock (lockOrientation)
    {
        MetroProgressBar pb = sender as MetroProgressBar;
        pb.AdjustToOrientationChange();

        if (pb.Orientation == Orientation.Horizontal)
        {
            pb.IsVertical = false;
            pb.IsHorizontal = true;
        }
        else
        {
            pb.IsVertical = true;
            pb.IsHorizontal = false;
        }

        pb.OnOrientationChanged(new OrientationChangedEventArgs((Orientation)e.OldValue, (Orientation)e.NewValue));
    }
}

/// <summary>
/// Causes the progress bar to reassign the extended border.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">Event information.</param>
private static void ExtendedBorderWidthValueChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
    //lock (lockExtendedBorder)
    {
        MetroProgressBar pb = sender as MetroProgressBar;
        pb.SetUpBorderParts();
    }
}

/// <summary>
/// Adjusts the progress bars visible progress rectangles after progress or visible changes.
/// </summary>
/// <param name="pb">The progress bar that changed.</param>
/// <param name="newValue">The new progress value. Only has to be set if there has been a progress change.</param>
private static void AdjustVisibleProgressRect(MetroProgressBar pb, double newValue = -1)
{
    if (pb.Orientation == Orientation.Horizontal)
    {
        pb.ProgressBarWidth = (newValue == -1 ? pb.Progress : newValue) / pb.MaxValue * pb.Width;
    }
    else
    {
        pb.ProgressBarHeight = (newValue == -1 ? pb.Progress : newValue) / pb.MaxValue * pb.Height;
    }
}

#endregion

#region Non-Static

/// <summary>
/// Adjusts the border ornaments to the new orientation of the control.
/// </summary>
private void AdjustToOrientationChange()
{
    SetUpBorderParts();
}

/// <summary>
/// Sets up the triangles that are placed on the left and right side of the progress bar.
/// </summary>
private void SetUpBorderParts()
{
    PointCollection leftBorder = new PointCollection();
    PointCollection rightBorder = new PointCollection();

    double borderWidth = ExtenedBorderWidth;

    if (Orientation == Orientation.Horizontal)
    {
        // Left triangle
        leftBorder.Add(new Point(0, 0));
        leftBorder.Add(new Point(0, Height));
        leftBorder.Add(new Point(Width * borderWidth, 0));

        // Right triangle
        rightBorder.Add(new Point(Width, 0));
        rightBorder.Add(new Point(Width, Height));
        rightBorder.Add(new Point(Width - (Width * borderWidth), Height));
    }
    else
    {
        // Top border
        leftBorder.Add(new Point(0, 0));
        leftBorder.Add(new Point(Width, 0));
        leftBorder.Add(new Point(0, Height * borderWidth));

        // Bottom border
        rightBorder.Add(new Point(0, Height));
        rightBorder.Add(new Point(Width, Height));
        rightBorder.Add(new Point(Width, Height - (Height * borderWidth)));
    }

    LeftBorderTriangle = leftBorder;
    RightBorderTriangle = rightBorder;
}

/// <summary>
/// Raises the Fnished event.
/// </summary>
/// <param name="e">Information on the event.</param>
protected virtual void OnFinished(EventArgs e)
{
    EventHandler handler = finished;

    if (handler != null)
    {
        handler(this, e);
    }
}

/// <summary>
/// Raises the ProgressChanged event.
/// </summary>
/// <param name="e">Information on the event.</param>
protected virtual void OnProgressChanged(ProgressChangedEventArgs e)
{
    EventHandler<ProgressChangedEventArgs> handler = progressChanged;

    if (handler != null)
    {
        handler(this, e);
    }
}

/// <summary>
/// Raises the OrientationChanged event.
/// </summary>
/// <param name="e">Information on the event.</param>
protected virtual void OnOrientationChanged(OrientationChangedEventArgs e)
{
    EventHandler<OrientationChangedEventArgs> handler = orientationChanged;

    if (handler != null)
    {
        handler(this, e);
    }
}

/// <summary>
/// Raises the RenderSizeChanged event and sets up the border parts.
/// </summary>
/// <param name="sizeInfo">Info on the size change.</param>
protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
{
    base.OnRenderSizeChanged(sizeInfo);
    SetUpBorderParts();
    AdjustVisibleProgressRect(this);
}

#endregion

      

+3


source to share


1 answer


I found the answer to my first problem ... Basically the progress bar Border element had its background property bound to Control-background and since it is after the Rectangle in the visual tree, it overlaid both of them.



The second problem came from the fact that I was using Height

both Width

instead of ActualHeight

and ActualWidth

in the custom control code. Therefore, when working with, for example, the / HorizontalAlignment.Stretch

properties are not set and therefore all calculations based on them do not work.Width

Height

0


source







All Articles