WPF - How To Tell What Raised The ComboBox_SelectionChanged Event
Is there any way to tell how the ComboBox_SelectionChanged event was raised in WPF.
That is, was the ev开发者_JAVA百科ent raised as the result of a user interaction, or as the result of a change in the property it is bound to?
In the ComboBox.SelectionChanged event, the sender is always the ComboBox and there is nothing in SelectionChangedEventArgs that will help you.
Two solutions to this occur to me. You could use a converter on the binding, or you could check the stack trace to see if System.Windows.Controls.Primitives.Selector.OnSelectedItemsCollectionChanged(object, NotifyCollectionChangedArgs) is in the stack. The stack check is extremely ugly, a bad practice, and won't work in partial trust environments. So I'll only describe the other one.
Using a converter on the binding to detect change sources
This solution is relatively clean, but requires a change to the binding. It also sometimes notifies you when things haven't changed.
Step 1: Create a converter that does no conversion but has a "Converted" event and a "ConvertedBack" event:
public EventingConverter : IValueConverter
{
public event EventHandler Converted;
public event EventHandler ConvertedBack;
public object Convert(object value, ...)
{
if(Converted!=null) Converted(this, EventArgs.Empty);
return value;
}
public object ConvertBack(object value, ...)
{
if(ConvertedBack!=null) ConvertedBack(this, EventArgs.Empty);
return value;
}
}
Step 2: Set your binding to use a new instance of this converter (don't share converter instances using a resource dictionary or a static property as is normally done)
<ComboBox ...>
<ComboBox.SelectedValue>
<Binding Path="..." ...>
<Binding.Converter>
<local:EventingConverter
Converted="ComboBoxSelectedValue_Converted"
ConvertedBack="ComboBoxSelectedValue_ConvertedBack" />
</Binding.Converter>
</Binding>
</ComboBox.SelectedValue>
</ComboBox>
Now your ComboBoxSelectedValue_Converted and ComboBoxSelectedValue_ConvertedBack methods will be called from within the binding process.
Warning: If you throw an exception in these events you'll break the binding.
If you can't modify the XAML that does the binding
If you have no control over the XAML that creates the binding (for example you are using attached properties) you can still come in and add the converter after the fact. In this case your converter class will need to chain to the previously declared converter, you'll have to clone the Binding and install the new one (they are immutable once they have been used), and you'll also have to deal with MultiBindings (if you want to support them).
Final note
The need to determine whether the change was made by the user or the property may in fact be a symptom of poor UI design, usually resulting from users who don't really understand their own requirements.
I've had several projects I've worked on where the end user specified that such-and-such was to happen "when I change this ComboBox". In almost every case it has turned out that the application would have behaved unexpectedly in certain use cases and we found a better way to accomplish the goal. In many cases what the user really wanted was "when this value first differs from the one in the database" or "when this value is no longer default" or "whenever this value is 5".
Short answer: no. There shouldn't be a difference, in both cases the selection has changed, that is all that matters. To determine if it was a user interaction, you will have to monitor a combination of other events, like DropDownOpened/Closed and KeyDown/Up, and the Stylus* ones.
I also met this kind of problem and solved it using a boolean bInternalChange
variable.
Imagine an interface converting °C to °F and back with two ComboBoxes. Selecting a value in the first updates the selection of the second, and selecting a value in the second updates the first. It creates an infinite loop if you don't distinguish UI changes from internal changes.
bool bInternalChange = false;
private void ComboBoxF_SelectionChanged(...)
{
if (!bInternalChange)
{
bInternalChange = true;
ComboBoxC.SelectedValue = ConvertFtoC(...);
bInternalChange = false;
}
}
private void ComboBoxC_SelectionChanged(...)
{
if (!bInternalChange)
{
bInternalChange = true;
ComboBoxF.SelectedValue = ConvertCtoF(...);
bInternalChange = false;
}
}
精彩评论