Пожалуйста, обратитесь к примеру 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"
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);
}
}
}