Автоматический возврат данных с использованием аргументов из каскадных комбинированных списков - PullRequest
0 голосов
/ 02 мая 2018

Пожалуйста, обратитесь к примеру MCVE ниже.

Мой проект использует API для извлечения данных об объекте ExampleObject.
Для выполнения других запросов через API требуется 3 элемента ID, Foo и, необязательно, Bar: GetDataPoint() в примере.

Я предоставляю 3 ComboBox для каждого из этих элементов, которые связаны с ObservableCollections.

Поскольку я хочу автоматически возвращать результаты, я вызываю GetDataPoint() каждый раз, когда любой ComboBox изменяет SelectedIndex.

Проблема:

GetDataPoint() вызывается чрезмерно ( 8 раз , когда Refresh() вызывается), и обычно только последний вызов в последовательности содержит допустимые аргументы.
Это проблема не только для производительности, но и вызовы API с неверными параметрами вызывают исключения в фоновом режиме.

Пример вывода на консоль:

Call: 1, ID: 4177, Foo: -1, Bar: -1  
Call: 2, ID: 4177, Foo: -1, Bar: -1  
Call: 3, ID: 4177, Foo: 32, Bar: 74  
Call: 4, ID: 4177, Foo: 32, Bar: 74  
Call: 5, ID: 4177, Foo: 32, Bar: -1  
Call: 6, ID: 4177, Foo: 32, Bar: 74  
Call: 7, ID: 4177, Foo: 32, Bar: 74  

Вопрос:

Есть ли способ вызвать GetDataPoint() ровно один раз, после того, как все аргументы действительны (после того, как Find_Foos() и Find_Bars() сделали свое дело), ​​когда выбранные ID, Foo или Bar изменятся ?

Пример проекта "CascadingDropDownLists"

GUI MainWindow.xaml

<Window x:Class="CascadingDropDownLists.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:CascadingDropDownLists"
        mc:Ignorable="d"
        Title="MainWindow" Height="200" Width="600">
    <Window.DataContext>
        <local:ViewModel/>
    </Window.DataContext>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <Button Margin="10" Content="Refresh" Width="100" 
                Command="{Binding RefreshCommand}" />
        <StackPanel Grid.Row="1" Margin="10" Orientation="Horizontal">
            <Label Content="Example Object ID: " Margin="20,0,0,0"/>
            <ComboBox MinWidth="80"
                      ItemsSource="{Binding IDCollection}" 
                      SelectedIndex="{Binding Selected_ID, Mode=TwoWay}"/>

            <Label Content="Foo: " Margin="20,0,0,0" />
            <ComboBox MinWidth="80"
                      ItemsSource="{Binding FooCollection}" 
                      SelectedIndex="{Binding Selected_Foo, Mode=TwoWay}"/>

            <Label Content="Bar: " Margin="20,0,0,0" />
            <ComboBox MinWidth="80"
                      ItemsSource="{Binding BarCollection}" 
                      SelectedIndex="{Binding Selected_Bar, Mode=TwoWay}"/>
        </StackPanel>
        <TextBlock Grid.Row="2" Margin="10" Text="{Binding Result}" />
    </Grid>
</Window>

ViewModel.cs

namespace CascadingDropDownLists
{
    using System;
    using System.ComponentModel;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;

    class ViewModel : INotifyPropertyChanged
    { 
        private ObservableCollection<int> _idCollection;
        public ObservableCollection<int> IDCollection
        {
            get { return _idCollection; }
            set { _idCollection = value;
                OnPropertyChanged("IDCollection");
            }
        }

        private int _selected_ID;
        public int Selected_ID
        {
            get { return _selected_ID; }
            set { _selected_ID = value;
                OnPropertyChanged("Selected_ID");
                Selected_ID_Changed();
            }
        }

        private ObservableCollection<int> _fooCollection;
        public ObservableCollection<int> FooCollection
        {
            get { return _fooCollection; }
            set { _fooCollection = value;
                OnPropertyChanged("FooCollection");
            }
        }

        private int _selected_Foo;
        public int Selected_Foo
        {
            get { return _selected_Foo; }
            set { _selected_Foo = value;
                OnPropertyChanged("Selected_Foo");
                Selected_Foo_Changed();
            }
        }

        private ObservableCollection<int> _barCollection;
        public ObservableCollection<int> BarCollection
        {
            get { return _barCollection; }
            set { _barCollection = value;
                OnPropertyChanged("BarCollection");
            }
        }

        private int _selected_Bar;
        public int Selected_Bar
        {
            get { return _selected_Bar; }
            set { _selected_Bar = value;
                OnPropertyChanged("Selected_Bar");
                Selected_Bar_Changed();
            }
        }

        private string result;
        public string Result
        {
            get { return result; }
            set { result = value;
                OnPropertyChanged("Result");
            }
        }

        private bool AlwaysExecute = true;
        private DelegateCommand<bool> _refreshCommand;
        public DelegateCommand<bool> RefreshCommand
        {
            get
            {
                if (_refreshCommand == null)
                {
                    _refreshCommand = new DelegateCommand<bool>(
                        (s) => { Refresh(); },
                        (s) => { return AlwaysExecute; }
                        );
                }
                return _refreshCommand;
            }
        }

        public ViewModel()
        {
            IDCollection = new ObservableCollection<int>();
            FooCollection = new ObservableCollection<int>();
            BarCollection = new ObservableCollection<int>();
        }

        public void Refresh()
        {      
            DataFromModel.RefreshData();
            Find_ExampleObjects();
        }

        private void Selected_ID_Changed()
        {
            Find_Foos();
            Find_Bars();
            GetDataPoint();
        }

        private void Selected_Foo_Changed()
        {
            Find_Bars();
            GetDataPoint();
        }

        private void Selected_Bar_Changed()
        {
            GetDataPoint();
        }

        private void Find_ExampleObjects()
        {
            IDCollection.Clear();
            FooCollection.Clear();
            BarCollection.Clear();

            foreach (int id in DataFromModel.GetExampleIDs())
            {
                IDCollection.Add(id);
            }
            if (IDCollection.Count > 0)
            {
                Selected_ID = 0;
            }
        }

        private void Find_Foos()
        {
            FooCollection.Clear();
            BarCollection.Clear();

            if (IDCollection.Count > 0 && Selected_ID >= 0)
            {
               foreach (int foo in DataFromModel.GetFoos(IDCollection[Selected_ID]))
                {
                    FooCollection.Add(foo);
                }
            }
            if (FooCollection.Count > 0)
            {
                Selected_Foo = 0;
            }
        }

        private void Find_Bars()
        {
            BarCollection.Clear();

            if (IDCollection.Count > 0 && Selected_ID >= 0)
            {
                if (FooCollection.Count > 0 && Selected_Foo >= 0)
                {
                    foreach (int ot in DataFromModel.GetBars(IDCollection[Selected_ID], FooCollection[Selected_Foo]))
                    {
                        this._barCollection.Add(ot);
                    }
                }
            }

            if (BarCollection.Count > 0)
            {
                Selected_Bar = 0;
            }
        }


        private DateTime lastCalled = DateTime.Now;
        private int timesCalled = 0;

        private void GetDataPoint()
        {
            int ID;
            int Foo = -1;
            int Bar = -1;

            if (DateTime.Now.Ticks - lastCalled.Ticks >= 10000000)
            {
                lastCalled = DateTime.Now;
                timesCalled = 1;
                Console.WriteLine(Environment.NewLine + Environment.NewLine);
            }
            else { timesCalled++; }

            if (IDCollection.Count > 0 && Selected_ID >= 0)
            {
                ID = IDCollection[Selected_ID];

                if (this.FooCollection.Count > 0 && Selected_Foo >= 0)
                {
                    Foo = this.FooCollection[Selected_Foo];
                }

                if (this.BarCollection.Count > 0 && Selected_Bar >= 0)
                {
                    Bar = this.BarCollection[Selected_Bar];
                }

                Result = DataFromModel.GetSomeInterestingData(ID, Foo, Bar) + Environment.NewLine
                    + "Times GetDataPoint() Called in the last second: " + timesCalled.ToString();

                Console.WriteLine("Call: {0}, ID: {1}, Foo: {2}, Bar: {3}", timesCalled, ID, Foo, Bar);
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;
        protected void OnPropertyChanged(string name)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null)
            {
                handler(this, new PropertyChangedEventArgs(name));
            }
        }
    }


    /// <summary>
    /// Each Object must have an ID.
    /// Each Object will have at least 1 Foo, up to max 10.
    /// Each Foo must have a Foo Number (1 to 100)
    /// Each Foo might or might not contain any bars (1 to 100)
    /// </summary>
    public class ExampleObject
    {
        public int ID { get; set; }
        public List<Foo> Foos { get; private set; }

        public ExampleObject(Random rnd, int id)
        { 
            // Generate example data
            int howManyFoos = rnd.Next(1, 10);
            Foos = new List<Foo>();

            for (int f = 0; f < howManyFoos; f++)
            {
                int newFooNumber = rnd.Next(1, 100);
                Foo foo = new Foo(newFooNumber);

                int howManyBars = rnd.Next(0, 12);

                for (int b = 0; b < howManyBars; b++)
                {
                    int newBar = rnd.Next(1, 100);
                    foo.Bars.Add(newBar);
                }
                Foos.Add(foo);
            }

            ID = id;
        }
    }

    public class Foo
    {
        public int FooNumber { get; private set; }
        public List<int> Bars { get; set; }

        public Foo(int fooNumber)
        {
            FooNumber = fooNumber;
            Bars = new List<int>();
        }
    }

    // Mock-up of a data source API
    public static class DataFromModel
    {
        private static Dictionary<int, ExampleObject> HiddenStuff;

        static DataFromModel()
        {
            HiddenStuff = new Dictionary<int, ExampleObject>();
        }

        public static void RefreshData()
        {
            HiddenStuff.Clear();
            Random rnd = new Random();
            // Create up to 10 example objects
            for (int i = 0; i < 10; i++)
            {
                int newID = rnd.Next(1, 5000);
                if (!HiddenStuff.ContainsKey(newID))
                {
                    HiddenStuff.Add(newID, new ExampleObject(rnd, newID));
                }
            }            
        }

        public static List<int> GetExampleIDs()
        {
            List<int> l = new List<int>();
            foreach (KeyValuePair<int, ExampleObject> kvp in HiddenStuff)
            {
                l.Add(kvp.Key);
            }
            return l;
        }

        public static List<int> GetFoos(int id)
        {
            List<int> foos = new List<int>();

            if (HiddenStuff.ContainsKey(id))
            {
                foreach (Foo f in HiddenStuff[id].Foos)
                {
                    foos.Add(f.FooNumber);
                }
            }
            return foos;
        }

        public static List<int> GetBars(int id, int fooNumber)
        {
            List<int> bars = new List<int>();

            if (HiddenStuff.ContainsKey(id))
            {
                foreach (Foo f in HiddenStuff[id].Foos)
                {
                    if (f.FooNumber == fooNumber)
                    {
                        foreach (int bar in f.Bars)
                        {
                            bars.Add(bar);
                        }
                    }
                }
            }
            return bars;
        }

        public static string GetSomeInterestingData(int id, int foo, int bar = -1)
        {
            if (bar == -1)
            {
                return "Something Returned: " + id.ToString() + ", " + foo.ToString();
            }
            else
            {
                return "Something Returned: " + id.ToString() + ", " + foo.ToString() + ", " + bar.ToString();
            } 
        }
    }
}

DelegateCommand.cs

using System;

namespace CascadingDropDownLists
{
    public class DelegateCommand<T> : System.Windows.Input.ICommand
    {
        private readonly Predicate<T> _canExecute;
        private readonly Action<T> _execute;

        public DelegateCommand(Action<T> execute)
            : this(execute, null)
        {
        }

        public DelegateCommand(Action<T> execute, Predicate<T> canExecute)
        {
            _execute = execute;
            _canExecute = canExecute;
        }

        public bool CanExecute(object parameter)
        {
            if (_canExecute == null)
                return true;

            return _canExecute((parameter == null) ? default(T) : (T)Convert.ChangeType(parameter, typeof(T)));
        }

        public void Execute(object parameter)
        {
            _execute((parameter == null) ? default(T) : (T)Convert.ChangeType(parameter, typeof(T)));
        }

        public event EventHandler CanExecuteChanged;
        public void RaiseCanExecuteChanged()
        {
            if (CanExecuteChanged != null)
                CanExecuteChanged(this, EventArgs.Empty);
        }
    }
}

1 Ответ

0 голосов
/ 03 мая 2018

Мне удалось решить эту проблему с помощью своего рода блокировки.

Теперь GetDataPoint() вызывается только один раз, и это всегда происходит после нахождения foos и баров, поэтому у меня есть действительные аргументы для передачи.

Решение:

    private bool ignoreSelectionChanges = false;

    private void Selected_ID_Changed()
    {
        ignoreSelectionChanges = true;
        Find_Foos();
        Find_Bars();
        ignoreSelectionChanges = false;

        GetDataPoint();
    }

    private void Selected_Foo_Changed()
    {
        if (!ignoreSelectionChanges)
        {
            ignoreSelectionChanges = true;
            Find_Bars();
            ignoreSelectionChanges = false;

            GetDataPoint();
        }
    }

    private void Selected_Bar_Changed()
    {
        if (!ignoreSelectionChanges)
        {
            GetDataPoint();
        }
    }
...