DataGrid Selected Row of an ObservableCollection

Topics: Actions & Coroutines, Getting Started
Jun 6, 2012 at 8:07 PM
Edited Jun 11, 2012 at 8:28 PM

I have a ViewModel that has a DataGrid, the DataGrid is bound to an ObservableCollection. I am able to add to the collection and I use an IWindowManager to popup that you fill the data in to add to the collection. Now I am trying to edit a selected row in the DataGrid, I am able to populate the dialogue box in the IWindow with whatever row index I specify. How do I make this the row that is selected in the DataGrid?

It looks something like this in the ViewModel:

        private ObservableCollection _collection= new ObservableCollection();
        public ObservableCollection Collection
        {
            get { return _collection; }
            set { _collection= value; }
        }

        public void AddCollection()
        {
            /* Modal Window */
            _windowManager.ShowDialog(new EditCollectionViewModel(_events));
        }

        public void EditCollection()
        {
            /* Modal Window */
            _windowManager.ShowDialog(new EditCollectionViewModel(/* SelectedRow */));
        }
Jun 12, 2012 at 9:57 AM

There are some default names of properties that CM will look in your viewmodel to bind the SelectedItem on the Datagrid. These names are derived from the name of the datagrid. 

If your datagrid is named x:Name="Items" then CM will singularize and look for properties like "ActiveItem", "SelectedItem", etc.

 

Just create a property with a name matching these conventions and you will have always the SelectedItem property bound to this property. So instead you work with indexes, you can work with the object that is in the row of datagrid.

Jun 13, 2012 at 4:51 PM
Edited Jun 13, 2012 at 4:53 PM

Thank you for replying jpolvora!

I have used this before on dropdowns, but not on selecting a whole row or collection.

i.e.

        public BindableCollection ListItem
        {
            get
            {
                return new BindableCollection(new string[] { "List Item 1",
                                                                    "List Item 2",
                                                                    "List Item 3",
                                                                    "List Item 4"
                });
            }
        }

        private string _selectedListItem;
        public string SelectedListItem
        {
            get { return _selectedListItem; }
            set
            {
                _selectedListItem = value;
                NotifyOfPropertyChange(() => SelectedListItem);
            }
        }

Does it matter that I have an "s" on the end (ie- ListItems and SelectedListItems)? Also, here I am making a string collection, does it matter that I am making a collection of a class?

Thank you again for all your help! I will give it a try without the "s" (ie- ListItems and SelectedListItems remove the "s") and see if that changes anything.

Jun 13, 2012 at 5:24 PM

I'm sure that will works if the property for the collection is plural name (e.g ListItems) and the property for selected item is singular (e.g SelectedListItem).

 

You can look into the source code for the conventions (ConvetionManager.cs)

        /// <summary>
        /// Derives the SelectedItem property name.
        /// </summary>
        public static Func<string, IEnumerable<string>> DerivePotentialSelectionNames = name =>{
            var singular = Singularize(name);
            return new[] {
                "Active" + singular,
                "Selected" + singular,
                "Current" + singular
            };
        };

Jun 13, 2012 at 5:30 PM
Edited Jun 13, 2012 at 5:32 PM

I tried it without "s" on the item names and it did not change anything, here is what I have.
View:

<Grid>
	<Button Content="[+] Add Item" Name="AddItem" Height="24" HorizontalAlignment="Left" Margin="12,14,0,0" VerticalAlignment="Top" Width="175" />
	<Button Content=" Edit Item" Name="EditItem" Height="24" HorizontalAlignment="Left" Margin="193,14,0,0" VerticalAlignment="Top" Width="175" />
	<DataGrid x:Name="Item"
		AutoGenerateColumns="False" Height="505" HorizontalAlignment="Left" Margin="12,44,0,0" VerticalAlignment="Top" Width="926">
		<DataGrid.Columns>
			<DataGridTextColumn Binding="{Binding ListItem1}" Header="List Item 1" IsReadOnly="True" />
			<DataGridTextColumn Binding="{Binding ListItem2}" Header="List Item 2" IsReadOnly="True" />
			<DataGridTextColumn Binding="{Binding ListItem3}" Header="List Item 3" IsReadOnly="True" />
			<DataGridTextColumn Binding="{Binding ListItem4}" Header="List Item 4" IsReadOnly="True" />
		</DataGrid.Columns>
	</DataGrid>
</Grid>

ViewModel:

private ObservableCollection<ItemClass> _item = new ObservableCollection<ItemClass>();
public ObservableCollection<ItemClass> Item
{
	get { return _item; }
	set { _item = value; }
}

private ObservableCollection<ItemClass> _selectedItem;
public ObservableCollection<ItemClass> SelectedItem
{
	get { return _selectedItem; }
	set
	{
		_selectedItem = value;
		NotifyOfPropertyChange(() => SelectedItem);
	}
}

public void AddItem()
{
	/* Modal Window */
	_windowManager.ShowDialog(new EditAirSealingViewModel(_events));
}

public void EditItem()
{
	/* Modal Window */
	_windowManager.ShowDialog(new EditAirSealingViewModel(_events, SelectedItem));
}
Jun 13, 2012 at 6:00 PM
Edited Jun 13, 2012 at 6:09 PM

So for some reason it is not connecting the Item with the SelectedItem. No matter how I do it, I think it has something to do with the ItemClass, for the fact that it works as a string. From what you posted:

public static Func<string, IEnumerable<string>> DerivePotentialSelectionNames

I am unable to find that line for the SelectedItem in my solution where is that exactly?

***Update

I found it, it is in the Caliburn Micro source code. It wasn't showing because it is compiled.

Jun 13, 2012 at 7:55 PM
Edited Jun 13, 2012 at 8:01 PM

SelectedItem property should not be a collection. The Items property should be a collection that holds all instances of the collection. 

 

private ObservableCollection<ItemClass> _items = new ObservableCollection<ItemClass>();
public ObservableCollection<ItemClass> Items
{
	get { return _items; }
}

SelectedItem is the property that holds the current highlighted item in datagrid:

    private ItemClass _selectedItem;
    public ItemClass SelectedItem
    {
	get { return _selectedItem; }
	set
	{
		_selectedItem = value;
		NotifyOfPropertyChange(() => SelectedItem);
	}
    }

Change the gridname to Items:

    <DataGrid x:Name="Items" ...>
Now CM will bind correctly to your properties.

Jun 13, 2012 at 8:01 PM

That solved it! Thank you so much jpolvora!

Oct 18, 2013 at 12:07 PM
Nov 27, 2013 at 1:31 PM
Hello. I'm new here, and i somehow have the same problem as gmaniac. I have a ViewModel (UsersViewModel) and a datagrid in UsersView. I'm trying so hard to bind the datagrid and i can't figure out why.

UsersModelView:
private ObservableCollection<UserDTO> users;
public ObservableCollection<UserDTO> Users
        {
            get
            {
                return users;
            }

            private set
            {
                users = value;
            }
        }
UsersView:
       <DataGrid x:Name="Users" ItemsSource="{Binding Users}" AutoGenerateColumns="False" AlternatingRowBackground="LightBlue" 
                  AlternationCount="2" IsReadOnly="True">
            <DataGrid.Columns>
                <DataGridTextColumn Header="First Name" Binding="{Binding FirstName}"/>
                <DataGridTextColumn Header="Last Name" Binding="{Binding LastName}"/>
                <DataGridTextColumn Header="Email" Binding="{Binding Email}"/>
            </DataGrid.Columns>
        </DataGrid>
it only works with "ItemsSource="{Binding Users}"", if i cut that out it won't work. can anyone please tell me why?
Nov 27, 2013 at 2:15 PM
Where is the datagrid defined? In a DataTemplate? A Style? An actual view?
Can you provide some logging from CM, providing a proper ILog to the framework?
Nov 28, 2013 at 8:58 AM
The datagrid is defined in an actual view. I'm not sure i can provide the logging from CM (dont really know how), but i will try.
Nov 30, 2013 at 4:25 PM
Edited Nov 30, 2013 at 4:43 PM
reference samples and you can grab a sample of the SimpleLog.cs, from GameLibrary sample. then you setup logging in the bootstrapper with this

LogManager.GetLog = type => new SimeLog(type);

in the viewmodel in question you then get a logger via

ILog logger = LogManager.GetLog(typeof(UsersViewModel);

using logger then you can do some of your own messages but everything that CM does will automatically get dumped to the output window with the line in the bootstrapper.

Also from your code posted, you are double binding, choose one or the other... Either via convention or "{Binding Users}".