WPF Binding Combox with different List and differe

2020-05-08 07:59发布

问题:

In my UserControl ucStep2 I have DataContext of Step2InfoData object that has several properties along with :

private string rockDensUnit;
public string RockDensity_Unit 
{
    get { return rockDensUnit; }
    set
    {
        if (rockDensUnit != value)
        {
            rockDensUnit = value;
            Changed("RockDensity_Unit");
        }
    }
}

In my app I got to bind several combo's with different normally measurement types Like {kg/m3, gm/m3}, {meter, cm} and so on such groups of measures. I mean, multiple combo's to have list of same items. So I preferred to create Class's of such lists that I can use in multiple combos. I created ComboItems.cs which contains all items lists that I will need to populate the drop down.

ComboItems.cs

//**OBJECTS I USE FOR LIST OF IEMS** 
// Class for kg, gm
public class KgGmItems
{
    public ObservableCollection<string> KgGmList { get; set; }

    public KgGmItems()
    {
        KgGmList = new ObservableCollection<string>();
        KgGmList.Add("kg/m3");
        KgGmList.Add("gram/cm3");
    }

    public string ValueSelected { get; set; }  // Don't know if this is useful in my case
}

// Class for meter, cm
public class MtCmItems : INotifyPropertyChanged
{
    public MtCmItems()
    {
        Dict = new Dictionary<string, string>
        {
            {"meter", "meter"}, 
            {"centimeter", "centimeter"}
        };
    }

    //...
 }

XML i.e. ucStep2 View

<!-- As the objects KgGmItems doesn't contain in ucStep2.xaml.cs or Step2InfoData (that is bound to this UC) so add reference of those classes -->
<UserControl.Resources>
    <ObjectDataProvider x:Key="KgGmObj" ObjectType="{x:Type top:KgGmItems}" />
    <ObjectDataProvider x:Key="MtCmObj" ObjectType="{x:Type top:MtCmItems}" />
</UserControl.Resources>

  <ComboBox DataContext="{StaticResource KgGmObj}" ItemsSource="{Binding KgGmList}"  SelectedValue="{Binding Path=RockDensity_Unit, Mode=TwoWay}" SelectedIndex="0" 
   Background="#FFB7B39D" Grid.Row="5" Height="23" HorizontalAlignment="Left" Margin="401,61,0,0" Name="comboBox6" VerticalAlignment="Top" Width="84" Visibility="Hidden">
  </ComboBox>

I want to display ObservableCllection KgGmList items from KgGmItems class and bind the selected value to RockDensity_Unit of class Step2InfoData that is bound to this UserControl.

In the above combo, I am able to display all items in the drop down, also 1st item is selected by default. But the value is not bind to RockDensity_Unit; it's value remains null.

I want this to happen 2-way i.e. when RockDensity_Unit proeprtiy's value is set programmatically, the value should be selected in the drop down. Of course the value should exists in the list.

By default the 1st item should be selected.

UPDATE Added DependencyProperty in ucStep2.xaml.cs

public static readonly DependencyProperty RockDensityUnitProperty =
    DependencyProperty.Register("RockDensity_Unit", typeof(string), typeof(UserControl),
     new FrameworkPropertyMetadata("kg/m3", FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));  

public string RockDensity_Unit
{
    get { return this.GetValue(RockDensityUnitProperty) as string; }
    set { SetValue(RockDensityUnitProperty, value); }
}

XML

<ComboBox DataContext="{StaticResource KgGmObj}" ItemsSource="{Binding KgGmList}"  SelectedItem="{Binding Path=RockDensity_Unit, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ucStep2}}, Mode=TwoWay}" 
   Background="#FFB7B39D" Grid.Row="5" Height="23" HorizontalAlignment="Left" Margin="401,61,0,0" Name="comboBox6" VerticalAlignment="Top" Width="84" Visibility="Hidden">
</ComboBox>

ERROR

Error 1 The type reference cannot find a public type named 'ucStep2'. Line 74 Position 194. This refers to the combobox ", " after FindAncestor

DOUBT The RockDensity_Unit CLR property in Step2InfoData is untouched.

Why is the code not able to find ucStep2 ? FYI, I think this may be relevant :

<UserControl x:Class="WellBore.ucStep2"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
         xmlns:local="clr-namespace:WellBore.Models"
         xmlns:top="clr-namespace:WellBore"
         mc:Ignorable="d" 
         d:DesignHeight="870" d:DesignWidth="700" MaxHeight="970" MinHeight="700" MaxWidth="600">

回答1:

Ok, so let's get this binding working... first, I am using an item from your KgGmItems class to bind to the ComboBox. In this class you have a collection of string values to display in the drop down and a string property to bind to the ComboBox.SelectedItem... perfect! Now I'm assuming that you have an instance of this class in the Resources section called KgGmObj... let's keep it simple to start with:

<ComboBox DataContext="{StaticResource KgGmObj}" ItemsSource="{Binding KgGmList}" 
SelectedItem="{Binding ValueSelected, Mode=TwoWay}" />

This is all you need to setup the binding between the ComboBox and your class. One thing to note though, is that when you try to set the selected item from your code, it will only work if you set it to one of the actual items in the collection... I think that this doesn't really count when using strings, but it's important to know this anyway. If you were setting a custom class as the type of objects in the ComboBox instead, then you could set the selected item like this:

ValueSelected = KgGmList.Where(item => item.Name == "NameOfObjectToMatch").Single();

Or better like this if you had a uniquely identifiable property:

ValueSelected = KgGmList.Where(item => item.Id == Id).Single()

With your string values, you should be able to set the selected item from code like this:

ValueSelected = "Some value";

UPDATE >>> Ok, so let's have another go... I think that I may have enough information to go on now. I think that you want something like this:

<ComboBox DataContext="{StaticResource KgGmObj}" ItemsSource="{Binding KgGmList}" 
SelectedItem="{Binding RockDensity_Unit, Mode=TwoWay}" />

The problem with this is that you have set the DataContext of the ComboBox to your KgGmObj object. This means that the Framework is going to try to find a property named RockDensity_Unit in that object. I also see another potential problem in your definition of this property.

In order to bind from a UserControl xaml to its code behind, you need to use a DependencyProperty. You can find out how to implement these from the Dependency Properties Overview page at MSDN. So first, I would recommend that you implement your RockDensity_Unit property as a DependencyProperty.

Next, we have to find a way to that property from the ComboBox in the xaml... we can do that using a RelativeSource binding like this:

<ComboBox DataContext="{StaticResource KgGmObj}" ItemsSource="{Binding KgGmList}" 
SelectedItem="{Binding RockDensity_Unit, RelativeSource={RelativeSource Mode=
FindAncestor, AncestorType={x:Type ucStep2}}, Mode=TwoWay}" />

Now, if you have a DependencyProperty to bind to the SelectedItem property and your UserControl class is named ucStep2, this should all work... let me know how it goes.

UPDATE 2 >>>

Your error is because you have to add an XML namespace at the top of your XAML file... something like this:

xmlns:YourNamespace="clr-namespace:ApplicationName.FolderNameContainingClass"

Then you use it to reference your class like this:

...AncestorType={x:Type YourNamespace:ucStep2} ...

Also, in your DependencyProperty declaration, you're supposed to supply the name the type of your control, not UserControl, so change

Register("RockDensity_Unit", typeof(string), typeof(UserControl),

to

Register("RockDensity_Unit", typeof(string), typeof(NameOfYourUserControl),

Clearly... replace 'NameOfYourUserControl' with the actual name of your class that extends the UserControl.



回答2:

Use a Dictionary.

XAML

<ComboBox ItemsSource="{Binding Dict}"
          DisplayMemberPath="Value"
          SelectedValuePath="Key"
          SelectedValue="{Binding Prop}"/>

Code Behind

public Dictionary< ValueType, string > Dict { get; private set; }

private ValueType _prop;
public ValueType Prop
{
    get{ return _prop }
    set
    {
        _prop = value;
        NotifyPropertyChanged( "Prop" ); // Implement INotifyPropertyChanged
    }
}

public ViewModel()
{
    Dict = new Dictionary< ValueType, string >()
    {
         { value1, string1 },
         { value2, string2 },
         { value3, string3 }
    };
}