WPF自定义控件/控件模板

时间:2015-06-22 12:56:19

标签: c# wpf data-binding custom-controls datatemplate

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

  1. 我想为我的控件分配一个背景颜色,但是它会覆盖网格内的矩形,因此矩形变得不可见。
  2. 我尝试为ContentControl编写模板,但内容未按预期呈现,这意味着只有显示名称会显示每个进度条的文本。
  3. 我的自定义控件的模板(如果我感兴趣的是后面的代码,我也会添加它):

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

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

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

1 个答案:

答案 0 :(得分:0)

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

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