WPF自定义控件/控件模板

3
我正在构建一个带有自定义控件的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"/>
...  

顶部:应用了模板的ContentControl,左下角:设置了背景的自定义控件,右下角:未设置背景的自定义控件

图像顶部显示应用了模板的内容控件,底部显示了最后一个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

2
截图对于这种问题很有帮助。你的问题是除了一个 Background 之外还有更多的依赖属性,比如 AnotherBackground 吗? - Sinatr
@Sinatr 我想要附上一张截图,但是我还没有足够的声望来附加它... - Streamline
嘿,你好,我不太明白你所指的标记是哪个。最后一个 XAML 文件有两个进度条,它们显示在第二个网格列中。 - Streamline
宽度是否正确?它们似乎有点太小了,无法充分利用所有的空间...除此之外,在ControlTemplate内设置DataContext可能不是最好的选择。但是,除非控件实现中还有更多代码我们没有看到,否则任何这些都不应该影响您的控件可见性。 - almulo
@almulo 宽度应该是正确的,刚刚在两个进度条之间测试了一下控件。代码的其余部分不应影响矩形/控件的可见性。 - Streamline
1个回答

0
我找到了解决我的第一个问题的答案...基本上,进度条的边框元素的背景属性被绑定到控件背景上,由于它在可视树中位于矩形之后,因此覆盖了它们两个。
第二个问题是因为我在用户控件的代码中使用了HeightWidth而不是ActualHeightActualWidth。因此,当使用例如HorizontalAlignment.Stretch时,Width/Height属性未设置,因此所有基于它们的计算都无法工作。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接