Это правильный способ создания пользовательского представления с видимой привязкой коллекции - PullRequest
0 голосов
/ 15 мая 2019

При необходимости создать пользовательское представление и присоединить его к модели представления, которая предоставляет две наблюдаемые коллекции и функции для создания коллекций. Содержимое коллекций отображается в элементах управления spinbox и colorpick. Мой код сейчас работает, но я не уверен, что это правильный способ реализации привязки.

наблюдаемые коллекции связаны через DependencyProperty, но я видел, как люди упоминали об использовании INotifyCollectionChanged и ICollectionView, но не могли понять, как они будут получать отдельные элементы коллекции.

public partial class BinsColorControl : UserControl, INotifyPropertyChanged
{
public ObservableCollection<Color> BinsColors
{
        get { return (ObservableCollection<Color>)GetValue(BinsColorProperty); }
        set { SetValue(BinsColorProperty, value); }
}

public ObservableCollection<double> BinsValues
{
        get { return (ObservableCollection<double>)GetValue(BinsValueProperty); }
        set { SetValue(BinsValueProperty, value); }
}


public static readonly DependencyProperty BinsColorProperty =
      DependencyProperty.RegisterAttached("BinsColors", typeof(ObservableCollection<Color>),
         typeof(BinsColorControl),
         new UIPropertyMetadata(null, OnBinsColorCollectionChanged));

public static readonly DependencyProperty BinsValueProperty =
      DependencyProperty.Register("BinsValues", typeof(ObservableCollection<double>),
         typeof(BinsColorControl),
         new UIPropertyMetadata(null, OnBinsValueCollectionChanged));

private static void OnBinsColorCollectionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    BinsColorControl binsColorControl = d as BinsColorControl;

    var action = new NotifyCollectionChangedEventHandler(
            (o, args) =>
            {
                if (binsColorControl != null)
                {
                    binsColorControl._reloadUI();
                }
            }
            );


    if (binsColorControl  != null)
    {
        if (e.OldValue != null)
        {
                var coll = (INotifyCollectionChanged)e.OldValue;
                // Unsubscribe from CollectionChanged on the old collection
                coll.CollectionChanged -= action;
        }

        if (e.NewValue != null)
        {
                var coll = (ObservableCollection<Color>) e.NewValue;
                // Subscribe to CollectionChanged on the new collection
                coll.CollectionChanged += action;
                binsColorControl._reloadUI();
        }
        binsColorControl.OnPropertyChanged("NOBins");
     }
 }

 private static void OnBinsValueCollectionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        BinsColorControl binsColorControl = d as BinsColorControl;

        var action = new NotifyCollectionChangedEventHandler(
           (o, args) =>
           {
               if (binsColorControl != null)
               {
                   binsColorControl._reloadUI();
               }
           }
           );

        if (binsColorControl != null)
        {
            if (e.OldValue != null)
            {
                var coll = (INotifyCollectionChanged)e.OldValue;
                coll.CollectionChanged -= action;
            }

            if (e.NewValue != null)
            {
                var coll = (ObservableCollection<double>)e.NewValue;
                // Subscribe to CollectionChanged on the new collection
                coll.CollectionChanged += action;
                binsColorControl._reloadUI();
            }
        }

    }
    private void _reloadUI()
    {
        ....
    }
 }

метод realoadUI обновит или добавит элементы управления, я мог бы изменить это для большей ясности:

 private void _reloadUI()
 {
      if (BinsColors == null || BinsValues == null)
            return;

        #region colorPickers
        while (_colorPickers.Count > NOBins)
        {
            binStack.Children.Remove(_colorPickers.Last());
            _colorPickers.Remove(_colorPickers.Last());
        }
        for (int i = 0; i < NOBins; i++)
        {
            ColorPicker colPick;
            if (i < _colorPickers.Count)
            {
                colPick = _colorPickers[i];
            }
            else
            {
                colPick = new ColorPicker();
                colPick.Margin = new Thickness(5);
                _colorPickers.Add(colPick);
            }
            colPick.SelectedColorChanged -= _onBinsColorChanged;
            colPick.SelectedColor = BinsColors.ElementAt(i);
            colPick.SelectedColorChanged += _onBinsColorChanged;
        }

        #endregion colorPickers

        #region valuePickers
        while (_valuePickers.Count > 0 && _valuePickers.Count > NOBinsValues)
        {
            binStack.Children.Remove(_valuePickers.Last());
            _valuePickers.Remove(_valuePickers.Last());
        }
        for (int i = 0; i < NOBinsValues; i++)
        {
            DoubleUpDown valPick;
            if (i < _valuePickers.Count)
            {
                valPick = _valuePickers[i];
            }
            else
            {
                valPick = new DoubleUpDown();
                valPick.Margin = new Thickness(5);
                _valuePickers.Add(valPick);
            }
            valPick.ValueChanged -= _onBinsValueChanged;
            valPick.Value = BinsValues.ElementAt(i);
            valPick.ValueChanged += _onBinsValueChanged;
        }
        #endregion valuePickers

        if (NOBins > 0)
        {
            binStack.Children.Clear();
            for (int i = 0; i < NOBins - 1; i++)
            {

                binStack.Children.Add(_colorPickers[i]);
                if(NOBinsValues > i)
                    binStack.Children.Add(_valuePickers[i]);
            }
            if (_colorPickers.Count == NOBins)
            {
                 binStack.Children.Add(_colorPickers[NOBins - 1]);
            }
        }
 }

используемая для этого модель представления называется analysisViewModel:

public class analysisViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler CollectionsChanged;        

    public ObservableCollection<double> BinsValues { get; private set; }

    private void BinsValues_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
    {
        CollectionsChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(BinsValues)));
    }

    public ObservableCollection<Color> BinsColors { get; private set; }

    private void BinsColors_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
    {
        CollectionsChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(BinsColors)));
    }

    ....

    public ColorBinsViewModel()
    {
        BinsValues = new ObservableCollection<double>();
        BinsValues.CollectionChanged += BinsValues_CollectionChanged;
        BinsColors = new ObservableCollection<Color>();
        BinsColors.CollectionChanged += BinsColors_CollectionChanged;
        NOBins = 2;
        ...
    }

    public int NOBins
    {
        get
        {
            return BinsColors.Count;
        }
        set
        {
            if (value >= 0 && value < 25)
            {
                if (BinsColors.Count > value)
                {
                    while (value < BinsColors.Count)
                    {
                        BinsColors.RemoveAt(BinsColors.Count - 1);
                        BinsValues.RemoveAt(BinsValues.Count - 1);
                    }
                    if (BinsColors.Count < value)
                    {
                        while (BinsColors.Count < value - 1)
                        {
                            BinsColors.Add(new Color());
                            BinsValues.Add(new double());
                            BinsValues[BinsValues.Count - 1] = 0.0;
                        }
                        BinsColors.Add(new Color());
                    }
                }
            }
        }
    }
}

Пользовательский вид в настоящее время используется внутри сетки, которую я использую для создания вкладок. В этом представлении содержимого используется viewmodel и пользовательский вид, которые связаны через код, я использую viewmodel для визуализации части пользовательского интерфейса, поэтому быстрая факторизация была невозможна. Связывание между customView выполняется путем установки datacontext в конструкторе сетки следующим образом:

public partial class MapAnalyseTabContent : Grid, INotifyPropertyChanged
{
... 
public MapAnalyseTabContent()
    {
        InitializeComponent();
        AnalysisViewModel = new analysisViewModel ();
        binsColorsCtrl.DataContext = AnalysisViewModel;

        ...
    }

... }

со стороной xaml:

<Grid 
   xmlns:CustomControl="clr-namespace:MyApp.CustomControl"
   ...>
    ...
    <CustomControl:BinsColorControl x:Name="binsColorsCtrl" BinsColors="{Binding BinsColors}" BinsValues="{Binding BinsValues}"/>
</Grid>

вот код xaml для пользовательского представления

<UserControl x:Class="MyApp.CustomControl.BinsColorControl"
         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:MyApp.CustomControl"
         xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
         mc:Ignorable="d" 
         d:DesignHeight="450" Width="139.423">
<StackPanel HorizontalAlignment="Stretch"  VerticalAlignment="Top">
    <xctk:IntegerUpDown x:Name="NOBinsControl" Value="{Binding NOBins}" Margin="4,10,4,10" ></xctk:IntegerUpDown>
    <StackPanel  x:Name="binStack" HorizontalAlignment="Stretch" Margin="0" VerticalAlignment="Top">
    <xctk:ColorPicker x:Name="colorPick1" VerticalAlignment="Top" Margin="4,2,4,2"/>
    <xctk:DoubleUpDown x:Name="doubleSpin_bin1" VerticalAlignment="Top" Margin="4,2,4,2"/>
    <xctk:ColorPicker x:Name="colorPick2" VerticalAlignment="Top" Margin="4,2,4,2"/>
        <xctk:DoubleUpDown x:Name="doubleSpin_bin2" VerticalAlignment="Top" Margin="4,2,4,2"/>
        <xctk:ColorPicker x:Name="colorPick3" VerticalAlignment="Top" Margin="4,2,4,2"/>
    </StackPanel>
</StackPanel>
</UserControl>
...