Andrey1661
Andrey1661

Reputation: 3

Repeatable pattern in WPF

I'm trying to achieve quite a specific thing in WPF. I need to create a verifiable control, which will be binded to object of generic class like this

public interface IVerifiable
{
    bool Verified { get; set; }
}

public class VerifiableProperty<T> : INotifyPropertyChanged, IVerifiable
{
    private T _value;
    private bool _verified;

    public T Value
    {
        get => _value;
        set
        {
            if (Equals(value, _value)) return;
            _value = value;
            OnPropertyChanged();
        }
    }

    public bool Verified
    {
        get => _verified;
        set
        {
            if (value == _verified) return;
            _verified = value;
            OnPropertyChanged();
        }
    }
}

I bind controls this way

<TextBox Text="{Binding Path=Number.Value}" att:VerifiableControl.Verified="{Binding Path=Number.Verified}"
                                                 BorderBrush="{Binding Path=(att:VerifiableControl.Verified), Mode=OneWay,
                                                               Converter={StaticResource BackColorVerifiedConverter}}">
                                            <inter:Interaction.Triggers>
                                                <inter:EventTrigger EventName="LostFocus">
                                                    <inter:InvokeCommandAction Command="{Binding Path=DataContext.VerifyCurrentFieldCommand, ElementName=Root}"
                                                                               CommandParameter="{Binding Path=Number}"/>
                                                </inter:EventTrigger>
                                            </inter:Interaction.Triggers>
                                        </TextBox>

Generic value of Verifiable property object binds to text (or SelectedDate in case of DatePicker, etc.), border color binds to flag "Verified" via converter and also I bind interaction trigger for "FocusLost" event to ViewModel command that just set "Verified" flag of corresponding property object to true.

I really don't like the idea I have to copypaste this block many times with little changes. I know, I cannot put intercation trigger in style and I also couldn't find another way to make some kind of short pattern for that. So, is there a way to create some kind of short pattern for this xaml code where I could customize name of property to bind? Or maybe you could suggest another approach for what I want ot achieve?

Upvotes: 0

Views: 71

Answers (1)

Sir Rufo
Sir Rufo

Reputation: 19106

Use some attached properties and attach in there to the LostFocus event.

public static class VerifiableBehaviour
{
    public static bool GetVerified(UIElement obj) => (bool)obj.GetValue(VerifiedProperty);
    public static void SetVerified(DependencyObject obj, bool value) => obj.SetValue(VerifiedProperty, value);

    public static readonly DependencyProperty VerifiedProperty = DependencyProperty.RegisterAttached(
        "Verified", typeof(bool), typeof(VerifiableBehaviour),
        new FrameworkPropertyMetadata(false)
        {
            BindsTwoWayByDefault = true,
            DefaultUpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged,
        });

    public static bool GetTracking(UIElement obj) => (bool)obj.GetValue(TrackingProperty);
    public static void SetTracking(UIElement obj, bool value) => obj.SetValue(TrackingProperty, value);

    public static readonly DependencyProperty TrackingProperty = DependencyProperty.RegisterAttached(
        "Tracking", typeof(bool), typeof(VerifiableBehaviour),
        new PropertyMetadata(false, Tracking_PropertyChanged));

    private static void Tracking_PropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        UIElement element = d as UIElement;

        if ((bool)e.NewValue)
            element.LostFocus += Element_LostFocus;
        else
            element.LostFocus -= Element_LostFocus;
    }

    private static void Element_LostFocus(object sender, RoutedEventArgs e)
    {
        UIElement element = sender as UIElement;
        SetVerified(element, true);
    }
}

and bind it to your controls

<StackPanel>
    <StackPanel.Resources>
        <DataTemplate x:Key="VerifiableText">
            <StackPanel>
                <TextBox b:VerifiableBehaviour.Tracking="True" 
                         b:VerifiableBehaviour.Verified="{Binding Verified}" 
                         Text="{Binding Value}"/>
                <TextBlock>(Verified: <Run Text="{Binding Verified}"/>)</TextBlock>
            </StackPanel>
        </DataTemplate>
    </StackPanel.Resources>

    <TextBlock Text="MyProperty1"/>
    <ContentControl ContentTemplate="{StaticResource VerifiableText}" 
                    Content="{Binding MyProperty1}" IsTabStop="False"/>
    <Separator/>
    <TextBlock Text="MyProperty2"/>
    <ContentControl ContentTemplate="{StaticResource VerifiableText}" 
                    Content="{Binding MyProperty2}" IsTabStop="False"/>
    <Separator/>
    <TextBlock Text="MyProperty3"/>
    <ContentControl ContentTemplate="{StaticResource VerifiableText}" 
                    Content="{Binding MyProperty3}" IsTabStop="False"/>
    <Separator/>
    <TextBlock Text="MyProperty4"/>
    <ContentControl ContentTemplate="{StaticResource VerifiableText}" 
                    Content="{Binding MyProperty4}" IsTabStop="False"/>
    <Separator/>
</StackPanel>

Upvotes: 1

Related Questions