简体   繁体   中英

ComboBox SelectedItem not updating

I'm using a ComboBox (WPF 4.0) to show user defined paragraph styles for an editor app. The ComboBox has two columns:

(1) Unformatted name of the paragraph style
(2) Text "abcABC123", in some properties formatted according to the paragraph style in the first column

These are public properties of the user defined paragraph style class (where the last 3 are no ResourceKeys but variables containing ResourceKeys):

_NameInternal
_NameUI
_ResourceKey_background
_ResourceKey_foreground
_ResourceKey_fontFamily

Problem: ComboBox shows a SelectedItem. If I open a dialog, change one or more of the three Binding properties of the SelectedItem (Background, Foreground, FontFamily) and close dialog then the SelectedItem of the ComboBox is not updated. But if I drop it down it shows the new formatting.

Is there a way to solve this in Xaml instead of C#?

<Window.Resources>
    <local2:_2StylesPara x:Key="_2stylesPara" />
    <CollectionViewSource x:Key="_collectionViewSource_stylesPara" Source="{StaticResource _2stylesPara}">
        <CollectionViewSource.SortDescriptions>
            <!-- Requires 'xmlns:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase"' declaration. -->
            <scm:SortDescription PropertyName="_NameUI" Direction="Ascending"/>
        </CollectionViewSource.SortDescriptions>
    </CollectionViewSource>
</Window.Resources>

<ComboBox Name="_cbStylesPara" HorizontalAlignment="Left" 
          ItemsSource="{Binding Source={StaticResource _collectionViewSource_stylesPara}}" 
          SelectedValuePath="_NameInternal" IsSynchronizedWithCurrentItem="True" >
    <ComboBox.Resources>
        <local2:_2ResourceLookupConverter x:Key="_resourceLookupConverter"/>
    </ComboBox.Resources>
    <ComboBox.ItemTemplate>
        <DataTemplate>
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="100"/>
                    <ColumnDefinition Width="100" />
                </Grid.ColumnDefinitions>
                <Grid.RowDefinitions>
                    <RowDefinition />
                </Grid.RowDefinitions>
                <TextBlock Text="{Binding _NameUI}" Grid.Column="0" VerticalAlignment="Center" />
                <TextBlock Grid.Column="1" Text="abcABC123" Margin="3,0,0,0"
                           Background="{Binding _ResourceKey_background, Converter={StaticResource _resourceLookupConverter}}"
                           Foreground="{Binding _ResourceKey_foreground, Converter={StaticResource _resourceLookupConverter}}"
                           FontFamily="{Binding _ResourceKey_fontFamily, Converter={StaticResource _resourceLookupConverter}}"/>
            </Grid>
        </DataTemplate>
    </ComboBox.ItemTemplate>
</ComboBox>

Code behind:

public class _2ResourceLookupConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        return App.Current.TryFindResource(value);
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        return Binding.DoNothing;
    }
}

Here are the 2 classes for the user defined paragraph styles:

public class _2StylesPara : ObservableCollection<_2StylePara>
// ObservableCollection implements INotifyPropertyChanged
{
    public _2StylesPara(){}
}

public class _2StylePara
{
    public event PropertyChangedEventHandler PropertyChanged;

    // This method is not reached if Background, Foreground or FontFamily changes
    private void SetValue<T>(ref T property, T value, string propertyName = null)
    {
        if (object.Equals(property, value) == false)
        {
            property = value;

            if (this.PropertyChanged != null)
            {
                this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }

    private string _nameInternal = string.Empty;
    private string _nameUI = string.Empty;
    private string _resourceKey_background = string.Empty;
    private string _resourceKey_foreground = string.Empty;
    private string _resourceKey_fontFamily = string.Empty;
    private string _resourceKey_nameUI = string.Empty; 
    private string _resourceKey_style = string.Empty; 
    private Style _style = null;
    private ResourceDictionary _valuesRD = null;
    private string _pathToValuesRD = string.Empty;


    public string NameInternal
    {
        get { return this._nameInternal; }
        set { SetValue(ref this._nameInternal, value); }
    }

    public string NameUI
    {
        get { return this._nameUI; }
        set { SetValue(ref this._nameUI, value); }
    }

    public string ResourceKey_background
    {
        get { return this._resourceKey_background; }
        set { SetValue(ref this._resourceKey_background, value); }
    }

    public string ResourceKey_foreground
    {
        get { return this._resourceKey_foreground; }
        set { SetValue(ref this._resourceKey_foreground, value); }
    }

    public string ResourceKey_fontFamily
    {
        get { return this._resourceKey_fontFamily; }
        set { SetValue(ref this._resourceKey_fontFamily, value); }
    }

    public string ResourceKey_nameUI
    {
        get { return this._resourceKey_nameUI; }
        set { SetValue(ref this._resourceKey_nameUI, value); }
    }

    public string ResourceKey_style
    {
        get { return this._resourceKey_style; }
        set { SetValue(ref this._resourceKey_style, value); }
    }

    public Style Style
    {
        get { return this._style; }
        set { SetValue(ref this._style, value); }
    }

    public ResourceDictionary ValuesRD
    {
        get { return this._valuesRD; }
        set { SetValue(ref this._valuesRD, value); }
    }

    public string PathToValuesRD
    {
        get { return this._pathToValuesRD; }
        set { SetValue(ref this._pathToValuesRD, value); }
    }


    // Constructor
    public _2StylePara(Style sty, string styleNameInternal, string styleNameUI, string resourceKey_style, string resourceKey_nameUI,
                      string resourceKey_foreground, string resourceKey_background, string resourceKey_fontFamily,
                      ResourceDictionary valuesRD, string pathToValuesRD)
    {
        _style = sty;
        _nameInternal = styleNameInternal;                  // [ "_sty001" ]
        _nameUI = styleNameUI;                              // [ "Standard" ]

        _resourceKey_style = resourceKey_style;             // [ "_stylePara001" ]
        _resourceKey_nameUI = resourceKey_nameUI;           // [ "_nameUi001 ]
        _resourceKey_foreground = resourceKey_foreground;   // [ "_brush_textcolor001" ]
        _resourceKey_background = resourceKey_background;   // [ "_brush_backcolor001" ]
        _resourceKey_fontFamily = resourceKey_fontFamily;   // [ "_fontFamily001" ]

        _valuesRD = valuesRD;                               // This ResourceDictionary contains all style values
        _pathToValuesRD = pathToValuesRD;                   // [ "...\Resources\1ParaStyleValuesRD001.xaml" ]
    }
}

If I understood it properly, _ResourceKey_background and other properties are properties of your user defined paragraph style class that is contained in your _2sytlesPara collection. The behavior what you experience is when you change these properties in the background, the view is not updated.

In this case if you update you model (aka one of the UDF style class instances) the binding should be notified about the update. It is done by the INotifyPropertyChanged.PropertyChanged event that should be triggered by the model.

The binding automatically works the other way around, therefore your model is updated when you change something on the view.

The standard property pattern for notification is:

class Model : INotifyPropertyChanged
{
    private int _Name = default(int);
    public int Name
    {
        get { return _Name; }
        set
        {
            SetValue(ref this._Name, value);
        }
    }

    private void SetValue<T>(ref T property, T value, [CallerMemberName]string propertyName = null)
    {
        if (object.Equals(property, value) == false)
        {
            property = value;

            if (this.PropertyChanged != null)
            {
                this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

I hope I understood your problem and I could help.

UPDATE:

IObservableCollection informs the binding mechanizm only about additions and deletions in the collection. Changes of nested property values of the items still not going to be reflected automatically.

Regarding your code I would suggest to try the following modifications:

First of all, I would not recommend start public propertry names with underscore. According to MS naming conventions names started with underscore are usually private backing fields.

So, alter the corresponding properties like:

private string _ResourceKey_nameUI = string.Empty;
public string ResourceKey_nameUI
{
    get { return this._ResourceKey_nameUI; }
    set { SetValue(ref this._ResourceKey_nameUI, value); }
}

Also alter the bindings to the properties:

<TextBlock Grid.Column="1" Text="abcABC123" Margin="3,0,0,0" 
Background="{Binding ResourceKey_background, Converter={StaticResource _resourceLookupConverter}}" 
Foreground="{Binding ResourceKey_foreground, Converter={StaticResource _resourceLookupConverter}}"
FontFamily="{Binding ResourceKey_fontFamily, Converter={StaticResource _resourceLookupConverter}}"/>

UPDATE 2

WPF binding checks if the bound model instances implement INotifyPropertyChanged interface. Please modify your class declaration as:

public class _2StylePara : INotifyPropertyChanged
{
//...
}

Furthermore, when you change your values in the background, you should use the properties not the backing fields. So, when you change NameUI the SetValue method is going to be called, and it will notify the binding to refresh the TextBlock. The binding should also point to the property, not the backing field.

Therefore: Text="{Binding NameUI}" NOT Text="{Binding _NameUI}" Plese, mark as answer if it helped. Thanks.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM