MKing
MKing

Reputation: 508

How can I pass a reference to another control as an IValueConverter parameter?

I am binding some business objects to a WPF ItemsControl. They are displayed using a custom IValueConverter implementation used to produce the Geometry for a Path object in the DataTemplate as shown here:

<ItemsControl x:Name="Display" 
              Background="White"
              HorizontalAlignment="Stretch" 
              VerticalAlignment="Stretch"
              ItemsSource="{Binding ElementName=ViewPlaneSelector, 
                                    Path=SelectedItem.VisibleElements}" 
           >
    <ItemsControl.Resources>
        <!-- This object is just used to get around the fact that ConverterParameter 
        can't be a binding directly (it's not a DependencyProperty on a DependencyObject -->
        <this:GeometryConverterData 
            x:Key="ConverterParameter2"
            Plane="{Binding ElementName=ViewPlaneSelector, 
                            Path=SelectedItem}" />
        <DataTemplate DataType="{x:Type o:SlenderMember}">
            <Path Stroke="Blue" StrokeThickness=".5"
              Data='{Binding Converter={StaticResource SlenderMemberConverter}, 
                            ConverterParameter={StaticResource ConverterParameter2}}'
              ToolTip="{Binding AsString}">
            </Path>
        </DataTemplate>
    </ItemsControl.Resources>
</ItemsControl>

Note that the items for the ItemsControl are drawn from the ViewPlaneSelector (a ComboBox) SelectedItem.VisibleElements property. I need that same ViewPlaneSelector.SelectedItem in the SlenderMemberConverter to figure out how to display this element. I'm trying to get a reference to it into the converter by creating the intermediate GeometryConverterData object in the Resources section. This object exists solely to get around the problem of not being able to bind directly to the ConverterParameter property (as mentioned in the comments). Here is the code for the GeometryDataConverter class:

class GeometryConverterData : FrameworkElement {
    public static readonly DependencyProperty PlaneProperty =
        DependencyProperty.Register("Plane", typeof(ViewPlane), 
            typeof(GeometryConverterData), null, ValidValue);

    public static bool ValidValue(object o){
        return true;
    }

    public ViewPlane Plane {
        get{
            return GetValue(PlaneProperty) as ViewPlane;
        }set{
            SetValue(PlaneProperty, value);
        }
    }
}

I added the ValidValue function for debugging, to see what this property was getting bound it. It only and always gets set to null. I know that the ViewPlaneSelector.SelectedItem isn't always null since the ItemsControl has items, and it's items are drawn from the same property on the same object... so what gives? How can I get a reference to this ComboBox into my ValueConverter.

Or, alternately, why is what I'm doing silly and overly complicated. I'm as guilty as many of sometimes getting it into my head that something has to be done a certain way and then killing myself to make it happen when there's a much cleaner and simpler solution.

Upvotes: 1

Views: 4997

Answers (4)

JustinAngel
JustinAngel

Reputation: 16102

Sorry, but you're doing it wrong.

Your entire forum post is one code smell after another.
1. You're using Value Converters to convert a business entity to it's visual counterpart. That's a code smell in my book.
2. You're looking into sending a UI control to business logic.
3. Honest to god, someone here said "MultiBindings" (and beyond being right) my head imploded and there is now a black hole of nothingness where my brain used to be.

For more on my religious convictions regarding Value Converters, Trigger, MultiBindings and other worst practices for real-world LOB development - feel free to read the following thread on WPF Disciples.

However, Let's focus on getting you untangled. You need a - ViewModel.

Here's what you need to put inside that ViewModel:
1. Controls don't bind to each other. It's an abomination unseen since biblical times. Whenever somthing on a control changes (ComboBox.SelectedItem, TextBox.Text, whatever) bind that directly to a ViewModel. So, instead of binding to PlaneSelector.SelectedItem and PlaneSelector.SelectedItem.VisibleElements, bind the selectedItem back to the ViewModel.
2. Convert between your business data and your correponding visual representation in the ViewModel. So, have the view model return the geometric data.

Here's a rough draft of how a super simple ViewModel that does that look like:

public class myFormViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    private void InvokePropertyChanged(string propertyName)
    {
        PropertyChangedEventHandler changed = PropertyChanged;
        if (changed != null) changed(this, new PropertyChangedEventArgs(propertyName));
    }


    private object _planeSelectedItem;
    public object PlaneSelectedItem
    {
        get { return _planeSelectedItem; }
        set
        {
            _planeSelectedItem = value;
            InvokePropertyChanged("PlaneSelectedItem");
        }
    }

    public IEnumerable<KeyValuePair<string, Geometry>> VisibleElements
    {
        get
        { 
           foreach(var slenderMember in PlaneSelectedItem.VisibleElements)
            {
                yield return new KeyValuePair<string, Geometry>(slenderMember.AsString, ToGeometry(slenderMember));
            }
        }
    }
}

And here's roughly how that part of your control should look like:

<ComboBox SelectedItem="{Binding PlaneSelectedItem, Mode=TwoWay}" />
<ItemsControl x:Name="Display" 
      Background="White"
      HorizontalAlignment="Stretch" 
      VerticalAlignment="Stretch"
      ItemsSource="{Binding VisibleElements}">
    <ItemsControl.ItemTemplate>
        <DataTemplate >
            <Path Stroke="Blue" StrokeThickness=".5"
                  Data="{Binding Value}" ToolTip="{Binding Key}">
            </Path>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

Obviously, there's some missing code here (Setting the this.DataContent = new myFormViewModel(), using KeyValuePairs, setting the ComboBox ItemsSource, etc). But the core here is to simplify these crazy binding shenanigans.

Instead of trying to hack togather XAML to behave like code - just use code. It'll make your life much simpler.

Upvotes: 1

Anvaka
Anvaka

Reputation: 15823

You can't. Just give up...

No-no. Wait :). Use MultiBinding instead of binding. At least it looks like you are trying to do something that it let's you to do.

Hope this helps.

Upvotes: 1

luvieere
luvieere

Reputation: 37534

I'm not very sure, but have you tried using a DynamicResource in your ConverterParameter, as in

        <Path Stroke="Blue" StrokeThickness=".5"
          Data='{Binding Converter={StaticResource SlenderMemberConverter}, 
                        ConverterParameter={DynamicResource ConverterParameter2}}'
          ToolTip="{Binding AsString}">
        </Path>

?

Upvotes: 0

Trainee4Life
Trainee4Life

Reputation: 2273

You can always watch for binding errors in Output window. It always starts with System.Windows.DataError ...

Upvotes: 0

Related Questions