WPF Dependency Property

2019-03-03 08:03发布

问题:

I'm struggling to get my head around dependency properties in WPF, maybe because the use case I'm looking for is very specific and not well documented.

What I have is a custom control that looks like this (please do ignore all the terrible code, it is temporary!):

<UserControl x:Class="HydroAccessory.Controls.FillGraph"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
         xmlns:local="clr-namespace:HydroAccessory.Controls"
         mc:Ignorable="d"
         SizeChanged="FillGraph_SizeChanged"
         d:DesignHeight="200" d:DesignWidth="300">
<Grid>

    <TextBlock x:Name="PercentageTB" Text="Fill Percentage: 0%" />

</Grid>

I call it inside my main window like this:

<controls:FillGraph x:Name="HydroModel" />

and the code inside the control is as follows:

using System;
using System.Windows;
using System.Windows.Controls;

namespace HydroAccessory.Controls
{
public partial class FillGraph : UserControl
{
    private float percentage;
    public float Percentage
    {
        get
        {
            return percentage;
        }
        set
        {
            percentage = value;
            PercentageTB.Text = String.Format("Fill Percentage: {0}", percentage.ToString() + "%");
        }
    }

    public FillGraph()
    {
        InitializeComponent();
    }
}
}

All that I want to be able to do is say in my custom control call in the main window:

<controls:FillGraph x:Name="HydroModel" Percentage="{Binding FillPercentage}" />

(Where FillPercentage is something in another script that you don't need to worry about). The control will be expanded, so it needs to stay as a custom control. I understand I may need dependency properties, but after trying many different ways I cannot figure out how to do this seemingly simple task. Help is appreciated.

回答1:

There isn't a dependency property in your code.

This is a dependency property:

public partial class FillGraph : UserControl
{
    public FillGraph()
    {
        InitializeComponent();
    }

    public float Percentage
    {
        get { return (float)GetValue(PercentageProperty); }
        set { SetValue(PercentageProperty, value); }
    }

    //  Using a DependencyProperty as the backing store for Percentage.  This 
    //  enables animation, styling, binding, etc...
    public static readonly DependencyProperty PercentageProperty =
        DependencyProperty.Register("Percentage", typeof(float), 
                typeof(FillGraph), new PropertyMetadata(0.0f));
}

As Ayyappan Subramanian suggests, the propdp snippet in Visual Studio will help create the boilerplate. Just be careful with the parameters you pass to DependencyProperty.Register(), and make sure the default value you pass to new PropertyMetadata() is the correct type. 0.0f is a float. If you pass integer 0 for a float property, it'll throw an exception at runtime.

The regular property public float Percentage here is optional. It's just there for your code to use. XAML won't ever touch it at all (put breakpoints in the getter and setter if you doubt me). That's part of what's special about dependency properties.

And here's how to use it in your usercontrol XAML. Note the StringFormat parameter to the binding.

<Grid>
    <TextBlock 
        Text="{Binding Percentage, RelativeSource={RelativeSource AncestorType=UserControl}, StringFormat='Fill Percentage: {0:#.##}%'}" 
        />
</Grid>

Note: If your percentage is expressed in the range 0 to 1 rather than 0 to 100, use the p percent format instead. We'll use p2 for two digits after the decimal point. We omit the % because the format string provides that.

    <TextBlock 
        Text="{Binding Percentage, StringFormat='Fill Percentage: {0:p2}', RelativeSource={RelativeSource AncestorType=UserControl}}" 
        />

This XAML from your question is fine just as it is, assuming that the viewmodel has a FillPercentage property and correctly implements INotifyPropertyChanged:

<controls:FillGraph x:Name="HydroModel" Percentage="{Binding FillPercentage}" />