WPF 自定义 Control/Control 模板

WPF Custom Control/Control template

我正在构建一个带有自定义控件的 wpf 应用程序,到目前为止一切正常。
但是现在我遇到了两个问题:

  1. 我想为我的控件指定一个背景颜色,但是它覆盖了网格内的矩形,因此矩形变得不可见。
  2. 我尝试为 ContentControl 编写模板,但内容未按预期呈现,这意味着只有显示名称会与每个进度条的文本一起显示。

我的自定义控件的模板(如果对后面的代码感兴趣,我也会添加):

<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>  

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:

...
<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"/>
...  

图像顶部显示应用了模板的内容控件。底部显示了最后 xaml 中定义的两个进度条(左边有背景,右边没有)。 这是为控件定义的所有自定义 DP:

/// <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值改变了回调和实例方法:

#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

我找到了第一个问题的答案...基本上进度条的 Border 元素将其 Background-属性 绑定到 Control-background,因为它在可视化树中的 Rectangle 之后它覆盖了他们两个。

第二个问题是因为我在用户控件的代码中使用了HeightWidth而不是ActualHeightActualWidth。所以当使用例如HorizontalAlignment.Stretch Width/Height 属性未设置,因此所有基于它们的计算都不起作用。