使用级联组合框

时间:2018-05-02 20:51:53

标签: c# wpf mvvm combobox observablecollection

请参阅下面的MCVE示例。

我的项目使用API​​来检索有关对象ExampleObject的数据 在通过API进行其他查询之前,需要3个项IDFooBar GetDataPoint():示例中为GetDataPoint()

我为每个项目提供了3个ComboBox,它们绑定到ObservableCollections。

因为我想自动返回结果,所以当任何ComboBox更改SelectedIndex时,我都会调用GetDataPoint()

问题:

  过度调用

Refresh()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 被调用时 8次),通常只有序列中的最后一次调用包含有效参数。
  这不仅是性能问题,而且使用错误参数进行API调用会在后台抛出异常。

控制台输出示例:

GetDataPoint()

问题:

  

在选定的Find_Foos(),{所有参数有效后(Find_Bars()ID完成了他们的事情之后),是否有任何方法可以恰好调用Foo一次Bar {1}}或<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> 更改?

示例项目&#34; CascadingDropDownLists&#34;

GUI MainWindow.xaml

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();
            } 
        }
    }
}

ViewModel.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);
        }
    }
}

DelegateCommand.cs

{{1}}

1 个答案:

答案 0 :(得分:0)

我设法用一种锁来解决这个问题。

现在GetDataPoint()只被调用一次,它总是在找到foos和bar之后发生,所以我有有效的参数传递。

解决方案:

    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();
        }
    }