0

I have a ViewModel with an ObservableCollection, and a View using a Xaml-declared CollectionViewSource, which is bound to a DataGrid.ItemsSource.

The ViewModel has a command to create a new object and add it to the ObservableCollection. Also in the ViewModel, I am handling the CollectionChanged event, so I "know" when an Add happens.

My doubt is:

how can I make the just-added object selected in the DataGrid?

Notice that the addition and the changed event are all in the ViewModel, but the selection must happen in the View.

I don't mind solutions based on code-behind.

ViewModel:

public class TelaInicialViewModel : ViewModelBase
{
    public ObservableCollection<PacienteViewModel> Pacientes { get; set; }

    IRepositório<Paciente> _repositório_pacientes = new RepositórioPaciente();

    // CONSTRUTOR
    public TelaInicialViewModel()
    {
        var pacientes = _repositório_pacientes.Items.Select(p => new PacienteViewModel(p));
        Pacientes = new ObservableCollection<PacienteViewModel>(pacientes);
        Pacientes.CollectionChanged += Pacientes_CollectionChanged;
    }


    void Pacientes_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        switch (e.Action)
        {
            case NotifyCollectionChangedAction.Add:
                foreach (var added in e.NewItems)
                {
                    var p = added as PacienteViewModel;
                    if (p != null)
                    {
                        var novoPaciente = p.GetModel();  /////////  HERE I HAVE IT!!
                        _repositório_pacientes.Adicionar(novoPaciente);
                    }
                }
                break;
            case NotifyCollectionChangedAction.Replace:
                // ...
                break;
        }
    }


    void AdicionarPaciente()
    {
        var formulárioPaciente = new FormulárioCriaçãoPaciente();
        PacienteViewModel novoPaciente = formulárioPaciente.ExibirDiálogo(new Paciente());
        if (novoPaciente != null)
            //////// HERE I HAVE IT!!!
            Pacientes.Add(novoPaciente);
    }
    public ICommand ComandoAdicionarPaciente { get { return new RelayCommand(AdicionarPaciente); } }
}

View (Xaml)

<UserControl.Resources>

    <CollectionViewSource
        x:Key="PacientesViewSource"
        Source="{Binding Pacientes}"
        Filter="PacientesViewSource_Filter"/>

</UserControl.Resources>


<DockPanel>

    <DockPanel DockPanel.Dock="Top" Margin="0,10,0,0" >
        <TextBox x:Name="FiltroPacienteTextBox" Height="30"
            TextChanged="FiltroPacienteTextBox_TextChanged"
            DockPanel.Dock="Bottom" Margin="10,10,10,0"/>
        <Button x:Name="botaoNovoPaciente" Width="120" Content="Novo"
            HorizontalAlignment="Left" Height="30" Margin="10,0,0,0"
            Command="{Binding ComandoAdicionarPaciente}"/>
    </DockPanel>

    <DataGrid x:Name="pacienteDataGrid" Margin="0,10,0,0"
        AutoGenerateColumns="False"
        CanUserAddRows="False" CanUserDeleteRows="False"
        CanUserReorderColumns="False" CanUserResizeRows="False"
        ItemsSource="{Binding Source={StaticResource PacientesViewSource}}"
        IsSynchronizedWithCurrentItem="True">
    </DataGrid>

</DockPanel>        
heltonbiker
  • 26,657
  • 28
  • 137
  • 252
  • when Binding your itemsSource, Did you set the Mode and the UpdateSourceTrigger ?? – Med.Amine.Touil Nov 09 '15 at 16:34
  • @Med.Amine.Touil I did it now, but it's not working yet. When I add something, the new item is displayed but the datagrid keeps its previous selection. – heltonbiker Nov 09 '15 at 16:39
  • and for me also i missundertood your question. Well I think this one could help .http://stackoverflow.com/questions/10708806/how-to-raise-an-event-when-datagrid-itemssource-is-changed – Med.Amine.Touil Nov 09 '15 at 16:40
  • @Med.Amine.Touil interesting, but how would I set the selection to the last added item, from there on? – heltonbiker Nov 09 '15 at 16:43

2 Answers2

4

I would encourage you to stick with the MVVM pattern here. In this situation I usually add properties to the ViewModel that represent the selected item. This keeps all of the important logic in the ViewModel where it can be tested. I would add something like the following to your ViewModel (if your ViewModel is a DependencyObject you could use a DependencyProperty here, instead):

private Paciente _SelectedPaciente;
public Paciente SelectedPaciente
{
    get { return this._SelectedPaciente; }
    set
    {
        if (this._SelectedPaciente != value)
        {
            this._SelectedPaciente = value;
            this.RaisePropertyChanged("SelectedPaciente");
        }
    }
}

Then modify your DataGrid like this:

<DataGrid x:Name="pacienteDataGrid" 
          Margin="0,10,0,0"
          AutoGenerateColumns="False"
          CanUserAddRows="False"
          CanUserDeleteRows="False"
          CanUserReorderColumns="False"
          CanUserResizeRows="False"
          ItemsSource="{Binding Source={StaticResource PacientesViewSource}}"
          SelectedItem="{Binding SelectedPaciente, Mode=TwoWay}"
          IsSynchronizedWithCurrentItem="True">

And then modify your AdicionarPaciente like this:

void AdicionarPaciente()
{
    var formulárioPaciente = new FormulárioCriaçãoPaciente();
    PacienteViewModel novoPaciente = formulárioPaciente.ExibirDiálogo(new Paciente());
    if (novoPaciente != null)
    {
        //////// HERE I HAVE IT!!!
        Pacientes.Add(novoPaciente);
        this.SelectedPaciente = novoPaciente;
    }
}

Now you can write a unit test for you ViewModel to confirm that when the command to add a new Paciente is invoked the new Paciente is selected.

Jason Boyd
  • 6,839
  • 4
  • 29
  • 47
  • You're absolutely right. I cannot guarantee that I'm in the mood to do it right now (that is, now that I have something that works), but your way is the right way. Anyway, if you have synchronized collections in the view and viewmodel (synchronized somehow, I mean), you can have some trust that changes in one can cause changes in the other, if you create the right conditions. Thank you very much! – heltonbiker Nov 09 '15 at 18:16
  • Well, I ended up finding some energy and made the change. It was actually very quick and easy, and also solved some additional issues. Thanks again! – heltonbiker Nov 09 '15 at 19:45
  • No problem. Glad I could help. – Jason Boyd Nov 09 '15 at 19:53
2

Well, looking at some answers suggested by the comments, I found out a quite convenient way to do what I want:

View (code-behind)

public partial class TelaInicialView : UserControl
{
    CollectionViewSource _pacientes_source;

    public TelaInicialView()
    {
        InitializeComponent();

        Loaded += TelaInicialView_Loaded;
    }


    void TelaInicialView_Loaded(object sender, RoutedEventArgs e)
    {
        _pacientes_source = FindResource("PacientesViewSource") as CollectionViewSource;

        // FIRST STEP: CAST CollectionView.Source to INotifyCollectionChanged, and listen to its CollectionChanged event
        var source = _pacientes_source.Source as INotifyCollectionChanged;
        source.CollectionChanged += TelaInicialView_CollectionChanged;
    }

    void TelaInicialView_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        // SECOND STEP: WHEN YOU ADD SOMETHING, SELECT IT!
        if (e.Action == NotifyCollectionChangedAction.Add)
            pacienteDataGrid.SelectedItem = e.NewItems[0];
    }

    // ...
}
heltonbiker
  • 26,657
  • 28
  • 137
  • 252
  • This is a View-based (code-behind) solution, if you want a ViewModel-based (MVVM-pure), take a look at the accepted answer. – heltonbiker Nov 09 '15 at 19:46