Kian
Kian

Reputation: 1169

Binding the displayed pattern (Path) to data model/How to make paths the same dimensions

Background

[Feel free to skip this]

I'm building a program that deals with horses, their owners, and the owner's racing colours (silks). This question is about a UserControl, called SilksControl that acts as a view for the JockeySilks.

To represent the silks I use the following class of enums:

public class JockeySilks
{
    public BodyPatterns BodyPattern { get; set; }
    public Colour BodyColour1 { get; set; }
    public Colour BodyColour2 { get; set; }

    public SleevePatterns SleevePattern { get; set; }
    public Colour SleeveColour1 { get; set; }
    public Colour SleeveColour2 { get; set; }

    public CapPatterns CapPattern { get; set; }
    public Colour CapColour1 { get; set; }
    public Colour CapColour2 { get; set; }
}

As you can see, there's different patterns and colours for each element of the jockey silks. The main portion of each element is the [Item]Colour1, and the pattern is filled with [Item]Colour2.

The basic composition of the SilksControl is a ViewBox that contains a Canvas which in turn contains a number of Paths. I drew each of the patterns as Paths inside a child Canvas.

Here's a picture. In this example, CapPattern and BodyPattern are set to Plain, and ArmPattern is set to Chevrons.

The Problem

I'm trying to figure out the best way to set the pattern based on a WPF data binding. However, there's one issue: each pattern Path has different Canvas.Top and Canvas.Left values and dimensions. By "best way" I mean something that's simple, readable and easy to implement.

Approaches I've thought about

  1. Switching the path in code - maybe something like pthCapPattern = CapPatterns[SilksModel.CapPattern] where CapPatterns is a Dictionary<CapPattern,Path> or maybe accessing it from the resources
    • But not a binding, I'd have to implement some events and stuff
  2. Binding the content of some control/panel to the SilksModel.[Item]Pattern with a converter that generates/pulls a Path from resources/dictionary
    • Which control?
    • Could have to generate entire new Path
    • Somewhat resource intensive
  3. Have all the Paths in the XAML and change the visibility of each
    • This is just awkward and weird
  4. Figure out a way to reconcile the differences in dimensions then create 1 path and bind to its Path.Data property (probably have some StreamGeometry in the resources, using a converter to go from enum to StreamGeometry)
    • I have no idea how to give them the same dimensions and hence Canvas offsets. :(

So solution 4 is my preferred solution but as I mentioned, I have no idea how I could do it, and my Googling skills can't come up with anything useful. Failing that, solution 2 would be the next best thing but I don't know of any containers that offer the same functionality as a canvas and offer binding to the children/content.

EDIT 1:

<Canvas x:Name="SPatterns" Height="173" Canvas.Left="6.8" Canvas.Top="107" Width="236.6">
    <Path x:Name="Chevrons" Fill="{Binding SilksModel.BodyColour2, Converter={StaticResource DBColourToColorConverter}, ElementName=root" Height="134.125" Canvas.Left="1.087" Stretch="Fill" Stroke="Black" Canvas.Top="21.667" Width="234.168" Data="M21.750001,94.749999 L34.000002,117.66218 30.625003,133.62501 17.000006,113.32909 0.5,126.75 3.2500048,108.125 z M212.418,93.416999 L230.918,106.79199 233.668,125.41701 217.168,111.99609 203.543,132.292 200.168,116.32917 z M32.25,48.374999 L44.250004,72.249999 40.625004,90.249999 28.000003,68.581336 7.750001,82.249999 11.665709,64.166339 z M201.918,47.041991 L222.50229,62.833335 226.418,80.916991 206.168,67.248336 193.543,88.916999 189.918,70.916991 z M41,1.8329993 L55.000002,28.166337 51.66667,45.832999 37.333336,23.499837 16.666001,37.417269 21.66571,19.418135 z M193.168,0.5 L212.50229,18.085143 217.502,36.084262 196.83467,22.166836 182.50133,44.499991 179.168,26.833333 z" />
    <!-- More SleevePatterns -->
</Canvas>

Upvotes: 4

Views: 976

Answers (1)

Chris
Chris

Reputation: 8656

This might not be the cleanest solution, but would something like this work for you (Obviously you'd move the geometry initialisation out of the constructor)?

You could create your suggested Dictionary<CapPattern,Path> object and populate it with your path information, but also apply a Transform to the Geometry to give it your desired dimensions/offset, relative to the Canvas.

public partial class Horses : UserControl, INotifyPropertyChanged
{
    public enum CapPattern { ChevronPattern, SomeOtherPattern };
    public Dictionary<CapPattern, Geometry> Patterns { get; set; }

    private Geometry currentPath;
    public Geometry CurrentPath
    {
        get { return this.currentPath; }
        set 
        { 
            this.currentPath = value;
            NotifyPropertyChanged();
        }
    }

    public Horses()
    {
        Patterns = new Dictionary<CapPattern, Geometry>();          

        Patterns.Add(
            CapPattern.ChevronPattern,
            Geometry.Combine(
                Geometry.Parse("M21.750001,94.749999 L34.000002,117.66218 30.625003,133.62501 17.000006,113.32909 0.5,126.75 3.2500048,108.125 z M212.418,93.416999 L230.918,106.79199 233.668,125.41701 217.168,111.99609 203.543,132.292 200.168,116.32917 z M32.25,48.374999 L44.250004,72.249999 40.625004,90.249999 28.000003,68.581336 7.750001,82.249999 11.665709,64.166339 z M201.918,47.041991 L222.50229,62.833335 226.418,80.916991 206.168,67.248336 193.543,88.916999 189.918,70.916991 z M41,1.8329993 L55.000002,28.166337 51.66667,45.832999 37.333336,23.499837 16.666001,37.417269 21.66571,19.418135 z M193.168,0.5 L212.50229,18.085143 217.502,36.084262 196.83467,22.166836 182.50133,44.499991 179.168,26.833333 z"),
                Geometry.Empty, 
                GeometryCombineMode.Union, 
                new TranslateTransform(0, 0)));
        Patterns.Add(
            CapPattern.SomeOtherPattern, 
            Geometry.Combine(
                Geometry.Parse("M21.750001,94.749999 L34.000002,117.66218 30.625003,133.62501 17.000006,113.32909 0.5,126.75 3.2500048,108.125 z M212.418,93.416999 L230.918,106.79199 233.668,125.41701 217.168,111.99609 203.543,132.292 200.168,116.32917 z M32.25,48.374999 L44.250004,72.249999 40.625004,90.249999 28.000003,68.581336 7.750001,82.249999 11.665709,64.166339 z M201.918,47.041991 L222.50229,62.833335 226.418,80.916991 206.168,67.248336 193.543,88.916999 189.918,70.916991 z M41,1.8329993 L55.000002,28.166337 51.66667,45.832999 37.333336,23.499837 16.666001,37.417269 21.66571,19.418135 z M193.168,0.5 L212.50229,18.085143 217.502,36.084262 196.83467,22.166836 182.50133,44.499991 179.168,26.833333 z"), 
                Geometry.Empty, 
                GeometryCombineMode.Union, 
                new TranslateTransform(20, 30)));           

        InitializeComponent();
    }

    // INotifyPropertyChanged implementaton.

}

In my mock up, I've populated a ComboBox from that dictionary which sets a property CurrentPath, which is bound to a Path on the Canvas:

<Grid>
    <StackPanel>
        <ComboBox ItemsSource="{Binding Path=Patterns}"
                  SelectedValue="{Binding Path=CurrentPath}"
                  SelectedValuePath="Value"
                  DisplayMemberPath="Key"/>
        <Canvas>
            <Path Data="{Binding Path=CurrentPath}" Stroke="Black" StrokeThickness="1" />
        </Canvas>
    </StackPanel>
</Grid>

You would retain your binding for Fill and other properties.

Another approach could be to make a small Class which held your Path information, alongside the required Top, Left, Transform or any other information required to position the pattern. You could then bind a list of those objects to a ComboBox in a way similar to the above, and bind all the required properties on Canvas and Path to the properties of the currently selected object.

Edit:

You may also be able to configure your transforms in a ResourceDictionary along these lines:

<Path x:Name="Chevrons" Fill="{Binding SilksModel.BodyColour2, Converter={StaticResource DBColourToColorConverter}, ElementName=root" Data="M21.750001,94.749999 L34.000002,117.66218 30.625003,133.62501 17.000006,113.32909 0.5,126.75 3.2500048,108.125 z M212.418,93.416999 L230.918,106.79199 233.668,125.41701 217.168,111.99609 203.543,132.292 200.168,116.32917 z M32.25,48.374999 L44.250004,72.249999 40.625004,90.249999 28.000003,68.581336 7.750001,82.249999 11.665709,64.166339 z M201.918,47.041991 L222.50229,62.833335 226.418,80.916991 206.168,67.248336 193.543,88.916999 189.918,70.916991 z M41,1.8329993 L55.000002,28.166337 51.66667,45.832999 37.333336,23.499837 16.666001,37.417269 21.66571,19.418135 z M193.168,0.5 L212.50229,18.085143 217.502,36.084262 196.83467,22.166836 182.50133,44.499991 179.168,26.833333 z" Stroke="Black" StrokeThickness="1">
    <Path.RenderTransform>
        <TranslateTransform X="20" Y="120"/>
    </Path.RenderTransform>
</Path>

Upvotes: 1

Related Questions