简体   繁体   English

WPF自定义控件/控件模板

[英]WPF Custom Control/Control template

I'm building a wpf application with a custom control and everything worked so far. 我正在构建一个带有自定义控件的wpf应用程序,到目前为止一切正常。
But now i encountered two problems: 但现在我遇到了两个问题:

  1. I want to assign a background color to my control but that overlays the rectangle inside the grid, so the rectangle becomes invisible. 我想为我的控件分配一个背景颜色,但是它会覆盖网格内的矩形,因此矩形变得不可见。
  2. I tried to write a template for a ContentControl but the content does not render as expected, meaning only the display name does show up with the text of each progress bar. 我尝试为ContentControl编写模板,但内容未按预期呈现,这意味着只有显示名称会显示每个进度条的文本。

The template for my custom control (if the code behind is of interest i'll add that as well): 我的自定义控件的模板(如果后面的代码是有意义的,我也会添加它):

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

The template for the ContentControl: 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>

The xaml that displays the content: 显示内容的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,左下角:带背景设置的自定义控件,右下角:没有背景设置的自定义控件

Top of the image shows the content control with the template applied. 图像顶部显示应用了模板的内容控件。 The bottom shows the two progress bars as defined in the last xaml (left with background, right without). 底部显示了最后一个xaml中定义的两个进度条(左边是背景,右边没有)。 That are all custom DPs that are defined for the control: 这些都是为控件定义的自定义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));

The DP value changed callbacks and instance methods: 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

I found the answer to my first problem... Basically the Border element of the progress bar had its Background-property bound to the Control-background and since it is after the Rectangle in the visual tree it overlayed both of them. 我找到了第一个问题的答案......基本上,进度条的Border元素将其Background属性绑定到Control-background,因为它位于可视树中的Rectangle之后,它覆盖了它们。

The second problem occurred because i used Height and Width instead of ActualHeight and ActualWidth in the code of the user control. 出现第二个问题是因为我在用户控件的代码中使用了HeightWidth而不是ActualHeightActualWidth So when working with eg HorizontalAlignment.Stretch the Width / Height properties are not set and therefore all calculations based on them do not work. 因此,在使用例如HorizontalAlignment.Stretch ,未设置Width / Height属性,因此基于它们的所有计算都不起作用。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM