EDITED: Как было сказано в комментариях, я должен реализовать шаблон MVVM, я сделал именно это. Однако та же проблема все еще сохраняется. Поэтому я изменил вопрос соответственно:
У меня есть сетка данных, содержащая два столбца, привязанных к наблюдаемой коллекции (в классе MyNotes). Один столбец содержит поле со списком, а другой — текстовое поле. В коллекции хранятся ссылки на объекты Note, содержащие переменную перечисления (отображаемую в поле со списком) и строку (отображаемую в текстовом поле). Все работает нормально, за исключением SelectedItems (и, следовательно, SelectedItem). Когда программа построена и запущена, вы можете добавить новые строки в сетку данных (используя кнопки добавления/удаления), но после попытки редактирования (путем ввода текстового поля или поля со списком сетки данных) элементы selectedItems и selectedItem сетки данных терпят неудачу. Это видно по использованию кнопок добавления/удаления: выбранная строка не удаляется и новая строка не добавляется над выбранной строкой соответственно. Это результат симптома, связанного с тем, что свойство SelectedNote теряет свою привязку (я не знаю, почему это происходит, и когда я пытаюсь взломать повторную привязку, повторная привязка терпит неудачу?). Другой признак связан со свойством выбранных элементов, не отражающим то, что на самом деле отображается в сетке данных как выбранное (при просмотре в режиме отладки).
Я уверен, что эта проблема связана с проблемой с сеткой данных, что делает ее непригодной для моего случая.
Вот новый XAML (его контекст данных, модель представления, задается в XAML, а ParaTypes и headerText являются статическими ресурсами XAML):
<DataGrid x:Name ="dgdNoteLimits"
ItemsSource ="{Binding ParagraphCollection}"
SelectedItem ="{Binding Path=SelectedNote, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
AllowDrop ="True"
HeadersVisibility ="Column"
AutoGenerateColumns ="False"
CanUserAddRows ="False"
CanUserReorderColumns ="False"
CanUserSortColumns ="False"
BorderThickness ="0"
VerticalGridLinesBrush ="DarkGray"
HorizontalGridLinesBrush="DarkGray"
SelectionMode ="Extended"
SelectionUnit ="FullRow"
ColumnHeaderStyle ="{StaticResource headerText}">
<DataGrid.ItemContainerStyle>
<Style>
<Style.Resources>
<!-- SelectedItem's background color when focused -->
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}"
Color="Blue"/>
<!-- SelectedItem's background color when NOT focused -->
<SolidColorBrush x:Key="{x:Static SystemColors.ControlBrushKey}"
Color="Blue" />
</Style.Resources>
</Style>
</DataGrid.ItemContainerStyle>
<DataGrid.Columns>
<DataGridComboBoxColumn Header = "Note Type"
ItemsSource = "{Binding Source={StaticResource ParaTypes}}"
SelectedValueBinding= "{Binding Path=NoteType, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
TextBinding = "{Binding Path=NoteType, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
MinWidth = "115"
Width = "Auto">
</DataGridComboBoxColumn>
<DataGridTextColumn Header ="Description"
Binding="{Binding Path=NoteText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Width ="*">
<DataGridTextColumn.ElementStyle>
<Style TargetType="TextBlock">
<Setter Property="TextWrapping"
Value ="Wrap"/>
</Style>
</DataGridTextColumn.ElementStyle>
<DataGridTextColumn.EditingElementStyle>
<Style TargetType="TextBox">
<Setter Property="SpellCheck.IsEnabled"
Value ="true" />
<Setter Property="TextWrapping"
Value ="Wrap"/>
</Style>
</DataGridTextColumn.EditingElementStyle>
</DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
<StackPanel Grid.Row="1"
Margin="0, 0, 0, 16"
Orientation="Horizontal"
HorizontalAlignment="Right">
<Button Content="Add"
Width="72"
Margin="16,8,8,8"
Command="{Binding AddClickCommand}"/>
<Button Content="Remove"
Width="72"
Margin="16,8,8,8"
Command="{Binding RemoveClickCommand}"/>
</StackPanel>
Вот модель представления:
class MainWindowViewModel : INotifyPropertyChanged
{
MyNotes NotesCollection;
private bool canExecute;
private ICommand clickCommand;
public MainWindowViewModel()
{
this.NotesCollection = new MyNotes();
this.ParagraphCollection = this.NotesCollection.Notes;
this.canExecute = true;
}
private ObservableCollection<Note> paragraphCollection;
public ObservableCollection<Note> ParagraphCollection
{
get { return this.paragraphCollection; }
set
{
this.paragraphCollection = value;
RaisePropertyChanged(() => this.ParagraphCollection);
}
}
private Note selectedNote;
public Note SelectedNote
{
get { return this.selectedNote; }
set
{
if (this.selectedNote == value)
return;
this.selectedNote = value;
RaisePropertyChanged(() => this.SelectedNote);
}
}
public ICommand AddClickCommand
{
get
{
return this.clickCommand ?? (new ClickCommand(() => AddButtonHandler(), canExecute));
}
}
public void AddButtonHandler()
{
int noteIndex = 0;
Note aNote;
// what to do if a note is either selected or unselected...
if (this.SelectedNote != null)
{
// if a row is selected then add row above it.
if (this.SelectedNote.NoteIndex != null)
noteIndex = (int)this.SelectedNote.NoteIndex;
else
noteIndex = 0;
//create note and insert it into collection.
aNote = new Note(noteIndex);
ParagraphCollection.Insert(noteIndex, aNote);
// Note index gives sequential order of collection
// (this allows two row entries to have same NoteType
// and NoteText values but still note equate).
int counter = noteIndex;
// reset collection index so they are sequential
for (int i = noteIndex; i < this.NotesCollection.Notes.Count; i++)
{
this.NotesCollection.Notes[i].NoteIndex = counter++;
}
}
else
{
//if a row is not selected add it to the bottom.
aNote = new Note(this.NotesCollection.Count);
this.ParagraphCollection.Add(aNote);
}
}
public ICommand RemoveClickCommand
{
get
{
return this.clickCommand ?? (new ClickCommand(() => RemoveButtonHandler(), canExecute));
}
}
public void RemoveButtonHandler()
{
//delete selected note.
this.ParagraphCollection.Remove(selectedNote);
}
//boiler plate INotifyPropertyChanged implementation!
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged<T>(Expression<System.Func<T>> propertyExpression)
{
var memberExpr = propertyExpression.Body as MemberExpression;
if (memberExpr == null)
throw new ArgumentException("propertyExpression should represent access to a member");
string memberName = memberExpr.Member.Name;
RaisePropertyChanged(memberName);
}
protected virtual void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
и модель:
class MyNotes
{
public ObservableCollection<Note> Notes;
public MyNotes()
{
this.Notes = new ObservableCollection<Note>();
}
}
public enum NoteTypes
{
Header, Limitation, Warning, Caution, Note
}
public class Note
{
public int? NoteIndex { get; set; }
public NoteTypes NoteType { get; set; }
public string NoteText { get; set; }
public Note()
{
this.NoteIndex = null;
this.NoteType = NoteTypes.Note;
this.NoteText = "";
}
public Note(int? noteIndex): this()
{
this.NoteIndex = noteIndex;
}
public override string ToString()
{
return this.NoteType + ": " + this.NoteText;
}
public override bool Equals(object obj)
{
Note other = obj as Note;
if (other == null)
return false;
if (this.NoteIndex != other.NoteIndex)
return false;
if (this.NoteType != other.NoteType)
return false;
if (this.NoteText != other.NoteText)
return false;
return true;
}
public override int GetHashCode()
{
int hash = 17;
hash = hash * 23 + this.NoteIndex.GetHashCode();
hash = hash * 23 + this.NoteType.GetHashCode();
hash = hash * 23 + this.NoteText.GetHashCode();
return hash;
}
}
Существующие комментарии были высоко оценены (я многому научился и вижу ценность MVVM). Жаль только, что они не решили проблему. Но спасибо.
Поэтому, если кто-нибудь знает, как я могу решить эту проблему, то это будет очень признательно.