Многие объяснения уже присутствуют, чтобы объяснить, как это происходит и как это исправить, но вы также должны следовать рекомендациям, чтобы избежать NullPointerException
вообще.
См. также: A хороший список лучших практик
Я бы добавил, очень важно, хорошо использовать модификатор final
. Использование "окончательной" модификатор, когда это применимо в Java
Сводка:
final
для обеспечения хорошей инициализации. @NotNull
и @Nullable
if("knownObject".equals(unknownObject)
valueOf()
поверх toString (). StringUtils
StringUtils.isEmpty(null)
. Чтобы достичь этого под MVVM ....
1] Имейте прикрепленное поведение, которое обрабатывает событие SelectionChanged
ComboBox. Это событие вызывается с некоторыми аргументами события, имеющими флаг Handled
. Но установка его в true бесполезна для SelectedValue
привязки. Привязка обновляет источник независимо от того, было ли обработано событие.
2] Следовательно, мы настраиваем привязку ComboBox.SelectedValue
на TwoWay
и Explicit
.
3] Только когда ваша проверка удовлетворена и в окне сообщений говорится Yes
, когда мы выполняем BindingExpression.UpdateSource()
. В противном случае мы просто вызываем BindingExpression.UpdateTarget()
, чтобы вернуться к старому выбору.
В моем примере ниже у меня есть список KeyValuePair<int, int>
, связанный с контекстом данных окна. ComboBox.SelectedValue
связан с простым записываемым свойством MyKey
из Window
.
XAML ...
<ComboBox ItemsSource="{Binding}"
DisplayMemberPath="Value"
SelectedValuePath="Key"
SelectedValue="{Binding MyKey,
ElementName=MyDGSampleWindow,
Mode=TwoWay,
UpdateSourceTrigger=Explicit}"
local:MyAttachedBehavior.ConfirmationValueBinding="True">
</ComboBox>
Где MyDGSampleWindow
- это x: Имя Window
.
Код позади ...
public partial class Window1 : Window
{
private List<KeyValuePair<int, int>> list1;
public int MyKey
{
get; set;
}
public Window1()
{
InitializeComponent();
list1 = new List<KeyValuePair<int, int>>();
var random = new Random();
for (int i = 0; i < 50; i++)
{
list1.Add(new KeyValuePair<int, int>(i, random.Next(300)));
}
this.DataContext = list1;
}
}
И прикрепленное поведение
public static class MyAttachedBehavior
{
public static readonly DependencyProperty
ConfirmationValueBindingProperty
= DependencyProperty.RegisterAttached(
"ConfirmationValueBinding",
typeof(bool),
typeof(MyAttachedBehavior),
new PropertyMetadata(
false,
OnConfirmationValueBindingChanged));
public static bool GetConfirmationValueBinding
(DependencyObject depObj)
{
return (bool) depObj.GetValue(
ConfirmationValueBindingProperty);
}
public static void SetConfirmationValueBinding
(DependencyObject depObj,
bool value)
{
depObj.SetValue(
ConfirmationValueBindingProperty,
value);
}
private static void OnConfirmationValueBindingChanged
(DependencyObject depObj,
DependencyPropertyChangedEventArgs e)
{
var comboBox = depObj as ComboBox;
if (comboBox != null && (bool)e.NewValue)
{
comboBox.Tag = false;
comboBox.SelectionChanged -= ComboBox_SelectionChanged;
comboBox.SelectionChanged += ComboBox_SelectionChanged;
}
}
private static void ComboBox_SelectionChanged(
object sender, SelectionChangedEventArgs e)
{
var comboBox = sender as ComboBox;
if (comboBox != null && !(bool)comboBox.Tag)
{
var bndExp
= comboBox.GetBindingExpression(
Selector.SelectedValueProperty);
var currentItem
= (KeyValuePair<int, int>) comboBox.SelectedItem;
if (currentItem.Key >= 1 && currentItem.Key <= 4
&& bndExp != null)
{
var dr
= MessageBox.Show(
"Want to select a Key of between 1 and 4?",
"Please Confirm.",
MessageBoxButton.YesNo,
MessageBoxImage.Warning);
if (dr == MessageBoxResult.Yes)
{
bndExp.UpdateSource();
}
else
{
comboBox.Tag = true;
bndExp.UpdateTarget();
comboBox.Tag = false;
}
}
}
}
}
В поведении я использую свойство ComboBox.Tag
для временного хранения пропущенного флага перепроверка, когда мы возвращаемся к старому выбранному значению.
Дайте мне знать, если это поможет.
Очень простое решение для .NET 4.5.1 +:
<ComboBox SelectedItem="{Binding SelectedItem, Delay=10}" ItemsSource="{Binding Items}" />
Это работает для меня в большинстве случаев. Вы можете откатить выделение в выпадающем списке, просто запустите NotifyPropertyChanged без присвоения значения.
Я предпочитаю пример кода «сплинтора», а не «AngelWPF». Их подходы довольно похожи, хотя. Я реализовал прикрепленное поведение CancellableSelectionBehavior, и оно работает так, как рекламируется. Возможно, это было просто, что код в примере сплинтора было легче подключить к моему приложению. Код в прикрепленном поведении AngelWPF содержал ссылки на тип KeyValuePair, который потребовал бы большего изменения кода.
В моем приложении был ComboBox, в котором элементы, отображаемые в DataGrid, основаны на элементе, выбранном в ComboBox. Если пользователь внес изменения в DataGrid, а затем выбрал новый элемент в ComboBox, я бы предложил пользователю сохранить изменения с помощью кнопок Да | Нет | Отмена в качестве параметров. Если они нажали «Отмена», я хотел игнорировать их новый выбор в ComboBox и сохранить старый выбор. Это сработало как победитель!
Для тех, кто пугает, когда видит ссылки на Blend и System.Windows.Interactivity, вам не нужно устанавливать Microsoft Expression Blend. Вы можете скачать Blend SDK для .NET 4 (или Silverlight).
О да, в моем XAML я действительно использую это как мое объявление пространства имен для Blend в этом примере:
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
Я сделал это аналогично тому, что сплинтор выше.
Ваше представление:
<ComboBox
ItemsSource="{Binding CompetitorBrands}"
DisplayMemberPath="Value"
SelectedValuePath="Key"
SelectedValue="{Binding Path=CompMfgBrandID,
Mode=TwoWay,
UpdateSourceTrigger=Explicit}" //to indicate that you will call UpdateSource() manually to get the property "CompMfgBrandID" udpated
SelectionChanged="ComboBox_SelectionChanged" //To fire the event from the code behind the view
Text="{Binding CompMFGText}"/>
Ниже приведен код для обработчика событий «ComboBox_SelectionChanged» из файла кода за представлением. Например, если вы просматриваете myview.xaml, имя файла кода для этого обработчика событий должно быть myview.xaml.cs
private int previousSelection = 0; //Give it a default selection value
private bool promptUser true; //to be replaced with your own property which will indicates whether you want to show the messagebox or not.
private void ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
ComboBox comboBox = (ComboBox) sender;
BindingExpression be = comboBox.GetBindingExpression(ComboBox.SelectedValueProperty);
if (comboBox.SelectedValue != null && comboBox.SelectedIndex != previousSelection)
{
if (promptUser) //if you want to show the messagebox..
{
string msg = "Click Yes to leave previous selection, click No to stay with your selection.";
if (MessageBox.Show(msg, "Confirm", MessageBoxButton.YesNo, MessageBoxImage.Question) == MessageBoxResult.Yes) //User want to go with the newest selection
{
be.UpdateSource(); //Update the property,so your ViewModel will continue to do something
previousSelection = (int)comboBox.SelectedIndex;
}
else //User have clicked No to cancel the selection
{
comboBox.SelectedIndex = previousSelection; //roll back the combobox's selection to previous one
}
}
else //if don't want to show the messagebox, then you just have to update the property as normal.
{
be.UpdateSource();
previousSelection = (int)comboBox.SelectedIndex;
}
}
}