简体   繁体   中英

XAML DependencyProperty with custom properties (class)

currently I have a boolean dependency property which is bound the following way:

public bool Status
{
    get { return (bool)GetValue(StatusProperty); }
    set { SetValue(StatusProperty, value); }
}

public static readonly DependencyProperty StatusProperty =
        DependencyProperty.Register("Status", typeof(bool),
        typeof(UIButton), new PropertyMetadata(false));

which I am using within a Trigger like this..

....
<Condition Binding="{Binding Path=Status}" Value="True" />
....

Everything works fine, but now I want to extend this boolean dependency property to something similar like that:

public class State : DependencyObject
{
    public bool? status
    {
        get { return (bool?)GetValue(statusProperty); }
        set { SetValue(statusProperty, value); }
    }

    public static readonly DependencyProperty statusProperty =
        DependencyProperty.Register("status", typeof(bool?),
        typeof(UIButton), new PropertyMetadata(false));

    public State()
    { 
    }

    public State(bool status)
    {
        this.status = status;
    }

    public override string ToString()
    {
        if (status)
            return "activated"; // this strings change on runtime, depending on user language.. simplified for demonstration purpose
        else
            return "deactivated"; // this strings change on runtime, depending on user language.. simplified for demonstration purpose
    }
}

My goal is, to override the ToString method of the boolean object or in other words, add a custom text if I bind this dependency property to a textbox like this

<TextBlock Text="{Binding Path=Status.status}"/>

but still can use it in my Trigger like shown above.

With the current code I'm getting XAML parse exceptions... not sure if this construct is valid.. I think not. :P

Any ideas on how I could implement such functionality?

Thanks.

EDIT

Currently I'm using this solution:

public class State
{
    public bool? status { get; set; }
    public string statusString {
        get {
            if (status == true)
                return "activated";
            else if (status == false)
                return "deactivated";
            else
                return "";
            }

        set {}
    }

    public State()
    { 
    }

    public State(bool? status)
    {
        this.status = status;
    }
}

with the following bindings:

<TextBlock Text="{Binding Path=Status.statusString}"/>

and

<Condition Binding="{Binding Path=Status.status}" Value="False" />

this works pretty well, the only disadvantage is, that I need to completly exchange the whole object, when changing the state.

myState = new State(false);

and not like...

myState.status = false;

Have you considered using a custom converter for this? It will convert a dependency property's value however you define. An example in your case could be:

public class StatusToTextConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        try
        {
            bool? b = (bool?)value;

            if (b == true) return "activated";
            else if (b == false) return "deactivated";
            else return "not set";
        }
        catch (Exception)
        {
            return "invalid";
        }
    }
    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        try
        {
            bool? returnValue = null;

            string s = (string)value;
            if (s == "activated") returnValue = true;
            else if (s == "deactivated") returnValue = false;

            return returnValue;
        }
        catch (Exception)
        {
            return null;
        }
    }
}

Then, in your XAML define StatusToTextConverter as a resource and use the converter in your binding expression on the TextBlock:

<UserControl.Resources>
    <local:StatusToTextConverter x:Key="statusToTextConverter"/>
</UserControl.Resources>

<TextBlock Text="{Binding Path=Status, Converter="{StaticResource statusToTextConverter"}"/>

This way your trigger condition will still be bound to the bool property but the TextBlock will display the bool value as something meaningful.

EDIT: I wrote that solution thinking you wanted to bind it to a TextBox, not a TextBlock. If you are just displaying the value, you don't need to define anything in the ConvertBack method and can simply throw a NotImplementedException.

Here is your solution;

public class State : UIElement
{

    public State()
    {

    }

    private bool _status;
    public bool Status
    {
        get { return (bool)GetValue(StatusProperty); }
        set
        {
            SetValue(StatusProperty, value);

            if (value != _status)
            {
                _status = value;

                if (value)
                {
                    this.StatusText = "Activated";
                }
                else
                {
                    this.StatusText = "Deactivated";
                }


                RaiseStatusChangedEvent();
            }


        }
    }

    // Using a DependencyProperty as the backing store for Status.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty StatusProperty =
        DependencyProperty.Register("Status", typeof(bool), typeof(State), new PropertyMetadata(InternStatusChanged));


    static void InternStatusChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        Nullable<bool> value = e.NewValue as Nullable<bool>;
        if (value.HasValue)
        {
            ((State)d).Status = value.Value;

        }


    }



    public string StatusText
    {
        get { return (string)GetValue(StatusTextProperty); }
        set { SetValue(StatusTextProperty, value); }
    }

    // Using a DependencyProperty as the backing store for StatusText.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty StatusTextProperty =
        DependencyProperty.Register("StatusText", typeof(string), typeof(State), new PropertyMetadata(""));



    // Create a custom routed event by first registering a RoutedEventID 
    // This event uses the bubbling routing strategy 
    public static readonly RoutedEvent StatusChangedEvent = EventManager.RegisterRoutedEvent(
        "StatusChanged", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(State));

    // Provide CLR accessors for the event 
    public event RoutedEventHandler StatusChanged
    {
        add { AddHandler(StatusChangedEvent, value); }
        remove { RemoveHandler(StatusChangedEvent, value); }
    }

    // This method raises the SelectedPathChanged event 
    void RaiseStatusChangedEvent()
    {
        RoutedEventArgs newEventArgs = new RoutedEventArgs(State.StatusChangedEvent);
        RaiseEvent(newEventArgs);
    }






}

Test:

public partial class MainWindow : Window
    {
        State state;

        public MainWindow()
        {
            InitializeComponent();

            state = new State();
            state.StatusChanged += new RoutedEventHandler(state_StatusChanged);
            state.Status = true;


        }

        void state_StatusChanged(object sender, RoutedEventArgs e)
        {

            MessageBox.Show(this.state.StatusText); 
        }
    }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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