0

EDITED: As comments have suggested, I should implement the MVVM pattern, I have done exactly that. However the same problem still persists. So I have altered the question accordingly:

I have a datagrid containing two columns bound to an observable collection (in the MyNotes class). One column contains a combobox and the other a textbox. The collection stores references to Note objects that contain an enumeration variable (displayed by the combobox) and a string (displayed by the textbox). All works fine except for the SelectedItems (and therefore the SelectedItem). When the program is built and run, you can add new rows to the datagrid (using the add/remove buttons) but after you attempt an edit (by entering the datagrid's textbox or combobox) then the datagrid's selectedItems and selectedItem fail. This can be seen by the use of the add/remove buttons: the selected row is not deleted and a new row is not added above the selected row respectively. This is a result of a symptom regarding the SelectedNote property losing its binding (I don't know why this happens and when I attempt to hack a rebind, the rebind fails?). Another symptom relates to the selected items property not reflecting what the datagrid is actually showing as selected (when viewing it in debug mode).

I am sure this problem relates to an issue with the datagrid, which makes it unusable for my case.

Here is the new XAML (its datacontext, the viewmodel, is set in the XAML and ParaTypes and headerText are both XAML static resources):

<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>

Here is the view model:

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));
        }
    }
}

and the model:

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;
    }
}

Existing comments were greatly appreciated (I have learnt a lot and see the value of MVVM). It is just a shame they have not resolved the problem. But thank you.

So if anyone knows how I can resolve this issue, then that would be greatly appreciated.

greenbeast
  • 364
  • 2
  • 8
  • 21
  • 2
    There is an incredible amount of complexity here. Part in due because you have a winforms approach to WPF. MVVM will save you a lot of time. – jamesSampica Sep 19 '13 at 16:06
  • Agreed with Shoe. The amount of code-behind for such a simple view is quite substantial. I imagine the reason you have your issue is because you're explcitly manipulating your view with the commands you call on it (e.g. GridRow.Remove or something). Instead, you should be manipulating the model (via a controller/viewmodel) and the UI should reflect any changes by proxy, not directly. – Adam Kewley Sep 19 '13 at 16:10
  • I have to be honest (and you have probably guessed). I don't get MVVM. I have looked at it before and it just makes no sense to me. So if you have any references of where to learn it (properly)...that would also be much appreciated. In particular what is the difference between it and MVP? – greenbeast Sep 19 '13 at 16:15
  • You can look at the links I posted in the answer I gave [here](http://stackoverflow.com/questions/18874258/wpf-adding-a-combobox-to-a-datagrid-or-list-view-based-on-different-db-tables/18874582#18874582). I'm unfamiliar with MVP, but I can tell you that if you are developing serious (over 1 week of development) WPF application you MUST use it. there is indeed a learning curve but once you get it and it becomes second nature to you, you'll understand it's true power. Use the force. Luke. – Omri Btian Sep 19 '13 at 19:13
  • I think your AddButtonHandler logic is wrong. If no item is selected it will ad a new note with index 0 at the bottom of the collection. – Dtex Nov 11 '13 at 11:27
  • thanks for the logic correction (I corrected above). The indexer was a part of a sort function to order the rows correctly (when they were loaded from an nhibernate output). – greenbeast Nov 11 '13 at 11:49

0 Answers0