2

So, here's my scenario: I have a ComboBox where the itemssource is a Dictionary<int, string> of various titles and their IDs. These titles can be disabled at some point in the future, and therefore shouldn't show in the ComboBox anymore. However, when viewing an old item, I still need to be able to show this old value in addition to the current active titles. I'll try to visualize a little better below.

Today:

The ComboBox items consist of

  • 1, Title1
  • 2, Title2
  • 3, Title3

Title3 is selected and the ID (3) is stored.

Tomorrow:

Title3 is disabled and Title4 is added, so the items now consist of

  • 1, Title1
  • 2, Title2
  • 4, Title4

However, if our value from yesterday is the value we're binding (ID 3), there is no matching item. Ideally, I'd like to append our old item to the end like so:

  • 1, Title1,
  • 2, Title2
  • 4, Title4
  • 3, Title3

There would obviously be separate lists for Enabled and Disabled titles, and any item that doesn't bind properly could then reference the Disabled titles variable.

I have investigated fallbackValues and even PriorityBindings but can't seem to find a way to make them fit what I'm trying to do. Perhaps some sort of Converter used with a fallbackValue? I appreciate the help and feedback.

Also, for reference, this is the code I'm currently working with (I'm looking to do this in a datagrid).

WPF:

<DataGridTemplateColumn x:Name="tkTitle" Header="Title" Width="150">
    <DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
            <ComboBox ItemsSource="{Binding DataContext.TaskTitles, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type DataGrid}}}" SelectedValue="{Binding Path=tkttID, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" SelectedValuePath="Key" DisplayMemberPath="Value" Width="Auto" />
        </DataTemplate>
    </DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>

Associated code in the ViewModel:

public Dictionary<int, string> TaskTitles
{
    get
    {
        return BestClass.taskTitles;
    }
}

EDIT - Working Code Here is the code I used to get everything working. The ItemsSource was updated to a MultiBinding with a Converter. The MultiBinding contains the Active TaskTitles, All TaskTitles, and the ID. These all get passed to the converter so the ID is checked in the active list, and added if it's not active. Thanks to all for the help!

WPF

<DataGridTemplateColumn x:Name="tkTitle" Header="Title" Width="150">
    <DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
            <ComboBox SelectedValue="{Binding Path=tkttID, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" SelectedValuePath="Key" DisplayMemberPath="Value" Width="Auto">
                <ComboBox.ItemsSource>
                    <MultiBinding Converter="{StaticResource CheckDisabledTaskTitle}" Mode="OneWay">
                        <MultiBinding.Bindings>
                            <Binding Path="DataContext.TaskTitles" RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType={x:Type DataGrid}}" />
                            <Binding Path="DataContext.AllTaskTitles" RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType={x:Type DataGrid}}" />
                            <Binding Path="tkttID" />
                        </MultiBinding.Bindings>
                    </MultiBinding>
                </ComboBox.ItemsSource>
            </ComboBox>
        </DataTemplate>
    </DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>

Codebehind for Converter

class CheckDisabledTaskTitle : IMultiValueConverter
{
    //values[0] - Active values
    //values[1] - All values (including disabled)
    //values[2] - Current ID
    public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        //Check to see if the active values contain our current ID
        if (((Dictionary<int, string>)values[0]).ContainsKey((int)values[2]))
        {
            //They do, so return just the active values
            return values[0];
        }
        else
        {
            //They don't, so we'll add only our disabled value to the active list
            ((Dictionary<int, string>)values[0]).Add((int)values[2], ((Dictionary<int, string>)values[1])[((int)values[2])]);
            //Then give that back
            return values[0];
        }
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}
jmgardn2
  • 981
  • 2
  • 8
  • 21

2 Answers2

2

If I understand what you're after here...

1) I believe you should modify things in your view-model - and supply a default if the result is null or empty (you need to implement your INotifyPropertyChanged to notify of changes there etc.).

2) The other option would be some sort of converter - which takes your list property and use MultiBinding.

For details see this post of mine (mostly the code there) - refreshing Value Converter on INotifyPropertyChanged

Basically you bind both your proper property list - and the 'default list' - and inside your converter you can make decisions what to do.

e.g.:

<ComboBox  ...>
    <ComboBox.ItemsSource>
        <MultiBinding Converter="{StaticResource ImageConverter}" Mode="OneWay">
            <MultiBinding.Bindings>
                <Binding Path="TaskTitles" />
                <Binding Path="FallbackTitles" />
                <!--Binding Path="" /-->
            </MultiBinding.Bindings>
        </MultiBinding>
    </ComboBox.ItemsSource>
</ComboBox>
Community
  • 1
  • 1
NSGaga-mostly-inactive
  • 14,052
  • 3
  • 41
  • 51
  • I think I've made some good progress using what you've provided here, but am hitting a snag. I'm trying to pass along the tkttID to the converter, so in there I can check to see if it's disabled and add in the extra title accordingly, but I get an exception adding a binding to the ConverterParameter. I may be approaching this the wrong way, but would appreciate some guidance. – jmgardn2 Apr 25 '13 at 17:54
  • as much as I have the time - np :) - did you implement your converter as `IMultiValueConverter` ? It takes an array of values - and just a tad different. If it doesn't help, try something and put up some code and I'll check that and let you know. – NSGaga-mostly-inactive Apr 25 '13 at 18:06
  • 1
    And I think this solution has all that you need - it's a standard pattern for dealing with more complex cases, passing more variable to 'make a choice' in the converter (or passing `this` etc. - you can do that with the 'commented line' - that passes the whole view-model if needed) – NSGaga-mostly-inactive Apr 25 '13 at 18:08
  • Ahhh ok, passing the ID in the extra binding is what I needed to know! Thank you very much for your help! I'll update my question with the code that ended up working. – jmgardn2 Apr 25 '13 at 18:32
0

I think you need to modify your collection in the ViewModel, so that you...

  1. Load Allowed Values (TaskTitles)
  2. Check for selected value (tkttID) in allowed values. If it does not exist, then add it.

To do this, you should give TaskTitles an auto property with a setter so that you can manage its contents somewhere else..

e.g... (below untested as i have no VS atm). I have also used Linq below, which is .NET 3 and above i think.

public Dictionary<int, string> TaskTitles {get;set;}

public void Initialise()
{
TaskTitles = new Dictionary<int, string>();

foreach(var taskTitle in BestClass.taskTitles)
    TaskTitles.Add(taskTitle);

if(!TaskTitles.Any(t => t.Value == tkttid.Value))
    TaskTitles.Add(tkttid);



}

Note that you are not adding to BestClass.taskTitles, so this collection will remain unaffected. You are creating a new collection and adding the same values in, plus (potentially) one other value.

Mike S
  • 166
  • 3
  • 10
  • Could you elaborate on the auto property with a setter? Not familiar. Thanks! – jmgardn2 Apr 25 '13 at 17:12
  • an auto property is just a property without a backing field.... public string MyProperty {get;set;} (In your example your "Backing field" is BestClass.TaskTitles).. the "{get;set;}" part is what makes it an auto property ... and by a setter i mean the "set;" bit - meaning that you can set the property from elsewhere in the code... i.e. the property is Writeable – Mike S Apr 25 '13 at 17:40
  • p.s. it doesnt have to be an auto property, it just looks nicer than the alternative... see http://stackoverflow.com/questions/9304/c-sharp-3-0-auto-properties-useful-or-not – Mike S Apr 25 '13 at 17:46
  • Ohh OK I know what they are just didn't put two and two together. I understand that now, but I'm not following how you would be able to pass along the tkttID to the initialize method... – jmgardn2 Apr 25 '13 at 17:50
  • well that bits up to you ;) .... i havent seen your full code so i dont know, but i had assumed that your view model has access to that field, since you are binding to it. – Mike S Apr 25 '13 at 17:56
  • Thanks for the help Mike! Ended up not being quite what I needed, but I appreciate the help! – jmgardn2 Apr 25 '13 at 18:38