0

I have a custom UserControl with a custom DependencyProperty. To paint a better picture of how I'm using this, the UserControl is the left navigation indicator in a wizard-like control. The left nav control exists inside of each of the controls that represent a step in the wizard. Inside of the left nav control, I am toggling visibility and setting visual properties of several child controls with a few converters with code similar to the following. I can't use a simple style selector or style-selecting converter because the entire structure of each row in my StackPanel is different if the item is selected or not.

This is a ton of code to be repeating all over my control to bind to a single custom property. Is there a shorter form version of the following or a cleaner way to implement this?

<Polygon
    Visibility="{Binding
    RelativeSource={RelativeSource Mode=FindAncestor,
    AncestorType=UserControl},
    Path=Selected,
    Converter={StaticResource myCustomConverter},
    ConverterParameter='Expected String'}">
...

The parent views supply a single property to customize the child control:

    <!-- Left Column -->
    <views:LeftNavControl Selected="Item to Select..." />
Rich
  • 36,270
  • 31
  • 115
  • 154

3 Answers3

1

There are some things you could do, but I would not describe them as neither a cleaner/shorter way to implement it. They are fairy easy to implement if you are using a MVVM approach.

The first thing you could do is getting rid of the RelativeSource/AncestorType in your bindings to find the location of the bound property. If both/all your controls (it's not clear how many controls you use) would share the same viewmodel you could bound the same viewmodel property to views:LeftNavControl.Selected, and to all of your toggled visibility controls.

The second thing you could do, is a more radical approach that would clean your xaml, and it will also make your myCustomConverter obsolete, but will move some of the business logic in your viewmodel. This works best in case you have a multitude of Polygons/other controls that need visibility toggle. You could have a StepXVisiblity property in your viewmodel, and you could calculate it every time views:LeftNavControl.Selected changes and your Polygon(s) xaml will look like:

<Polygon Visibility="{Binding StepXVisiblity}">

A simple example of the above explanation would be:

<StackPanel>
    <TextBox Text="{Binding Step, UpdateSourceTrigger=PropertyChanged}" />
    <TextBlock Text="One" Visibility="{Binding StepOneVisible}" />
    <TextBlock Text="One" Visibility="{Binding StepOneVisible}" />
    <TextBlock Text="Two" Visibility="{Binding StepTwoVisible}" />
    <TextBlock Text="Two" Visibility="{Binding StepTwoVisible}" />
</StackPanel>

ViewModel:

public class MyVM : DomainBase
{
    private int step;

    public int Step
    {
        get 
        { 
            return step; 
        }
        set 
        { 
            step = value;
            OnPropertyChanged("Step");
            OnPropertyChanged("StepOneVisible");
            OnPropertyChanged("StepTwoVisible");
        }
    }

    public Visibility StepOneVisible
    {
        get
        {
            return step == 1 ? Visibility.Visible : Visibility.Collapsed;
        }
    }

    public Visibility StepTwoVisible
    {
        get
        {
            return step == 2 ? Visibility.Visible : Visibility.Collapsed;
        }
    }
}
Andrei Gavrila
  • 853
  • 9
  • 19
  • I'm using a very lightweight MVVM implementation (no framework, just wrote some VM base classes and helpers), but I wasn't backing this control by a VM just yet because I didn't need it for anything else in the control. That's definitely the logical way to go about this. Thanks – Rich Jun 14 '12 at 10:47
0

The only thing that comes to mind is to create your own binding class that derives from the base one (which itself is a markup extension) and sets all of those properties to the defaults you normally use. That way you will only have to specify the things that aren't default.

Tim
  • 14,999
  • 1
  • 45
  • 68
0

First, since you said something along the lines of Navigation panel, you might want to think about using something like a listview or a listbox and templating your items while binded to a collection in your view model. But, since I don't know the scope of your Nav control, I will assume that your Nav control doesn't exactly fit this scheme.

I will suggest using Attached Properties to achieve what you described as setting multiple properties based on single parent property. Consider the following as pseudo-code since I haven't tested it:

    public class MagicHelper
    {
        #region Super Property


        public static readonly DependencyProperty SuperProperty =
            DependencyProperty.RegisterAttached(
                "Super", typeof(SecretType), typeof(MagicHelper)
                new PropertyMetadata(-1, SuperChanged));

        // Get
        public static SecretType GetSuper(DependencyObject obj)
        {
            return (SecretType)obj.GetValue(SuperProperty);
        }

        // Set
        public static void SetSuper(DependencyObject obj, SecretType value)
        {
            obj.SetValue(SuperProperty, value);
        }

        // Change Event - make stuff happen here
        public static void SuperChanged(
            DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {

// I am using Polygon here for example but you could use a base class that might cover all your controls
            if (!(obj is Polygon))
                return;


            Polygon pGon = (Polygon)obj;

        // do your thing to pGon here with e.NewValue
        var mySecretInstance = e.NewValue as SecretType;

        if (mySecretInstance.frap)
        {
        pGon.Visibility = Visibility.Collapsed;
        pGon.Background = Brushes.Red;
            }

...
...

        }

}

and the XAML goes something like this

<UserControl Name="thisControl" 
xmlns:local="yourLibrary">
<Polygon local:MagicHelper.Super="{Binding ElementName=thisControl, Path=Selected"/>
...

m

LadderLogic
  • 1,090
  • 9
  • 17