WPF Datagrid SelectedItem теряет привязку после редактирования

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). Жаль только, что они не решили проблему. Но спасибо.

Поэтому, если кто-нибудь знает, как я могу решить эту проблему, то это будет очень признательно.


person greenbeast    schedule 19.09.2013    source источник
comment
Здесь невероятная сложность. Частично, потому что у вас есть подход winforms к WPF. MVVM сэкономит вам много времени.   -  person James Sampica    schedule 19.09.2013
comment
Согласен с Шу. Объем кода программной части для такого простого представления весьма значителен. Я предполагаю, что причина, по которой у вас возникла проблема, заключается в том, что вы явно манипулируете своим представлением с помощью команд, которые вы ему вызываете (например, GridRow.Remove или что-то в этом роде). Вместо этого вы должны манипулировать моделью (через контроллер/модель представления), а пользовательский интерфейс должен отражать любые изменения через прокси, а не напрямую.   -  person Adam Kewley    schedule 19.09.2013
comment
Я должен быть честным (и вы, наверное, догадались). Я не получаю MVVM. Я смотрел на это раньше, и это просто не имеет смысла для меня. Так что, если у вас есть какие-либо ссылки на то, где этому научиться (правильно)... это тоже будет очень признательно. В частности, в чем разница между ним и MVP?   -  person greenbeast    schedule 19.09.2013
comment
Вы можете посмотреть ссылки, которые я разместил в ответе, который я дал здесь. Я не знаком с MVP, но могу сказать вам, что если вы разрабатываете серьезное (более 1 недели разработки) приложение WPF, вы ДОЛЖНЫ его использовать. действительно есть кривая обучения, но как только вы ее освоите и она станет вашей второй натурой, вы поймете ее истинную силу. Используй силу. Люк.   -  person Omri Btian    schedule 19.09.2013
comment
Я думаю, что ваша логика AddButtonHandler неверна. Если ни один элемент не выбран, будет добавлена ​​новая заметка с индексом 0 внизу коллекции.   -  person Dtex    schedule 11.11.2013
comment
спасибо за поправку логики (поправил выше). Индексатор был частью функции сортировки для правильного упорядочения строк (когда они загружались из вывода nhibernate).   -  person greenbeast    schedule 11.11.2013