Binding to a list within a collection WPF/VB - wpf

I was wondering if its possible to bind a datagrid column to a list (Of T) thats stored within an observable collection!
Here's my current code:
Dim _BindWithThis As New List(Of BindWithThis)
Me.DataContext = _BindWithThis
For i = 0 To 3
Dim NewList As New List(Of Double) From {i + 0.25, i + 0.5, i + 0.75}
_BindWithThis.Add(New BindWithThis() With _
{.InternalNum = i, .DataValue = NewList})
Next
DataGrid1.ItemsSource = _BindWithThis
Dim NC As New DataGridTextColumn
NC.Header = "New Column"
NC.Width = 85
Dim b As New Binding
b.Path = New PropertyPath("DataValue")
NC.Binding = b
DataGrid1.Columns.Add(NC)
This currently displays four rows of "(Collection)". Is it possible to step into one of these "Collection" rows and display the data? I know that this is possible to do with a list box by binding with a specific element in the collection:
ListBox1.ItemsSource = _BindWithThis.Item(0).DataValue
I just can't work out how to do this with a datagrid...
Thanks for any help!
James

Here's my example as promised. This uses the DataGrid.RowDetailsTemplate to allow you to expand/collapse your list of data. Apologies that it's C# and not VB.NET.
Xaml:
<Page.DataContext>
<Samples:DataGridRowDetails2ViewModel/>
</Page.DataContext>
<Grid>
<DataGrid x:Name="dataGrid" ItemsSource="{Binding Items}"
AutoGenerateColumns="False" IsReadOnly="True">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Name}" Header="Name" />
<DataGridTemplateColumn Header="Show details">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ToggleButton Content="Show details"
IsChecked="{Binding IsChecked}"
Checked="ToggleButtonChecked" Unchecked="ToggleButtonChecked"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
<DataGrid.RowDetailsTemplate>
<DataTemplate DataType="{x:Type Samples:DataItemWithDetails}">
<ItemsControl ItemsSource="{Binding Doubles}" />
</DataTemplate>
</DataGrid.RowDetailsTemplate>
</DataGrid>
</Grid>
C#
public partial class DataGridRowDetails2
{
public DataGridRowDetails2()
{
InitializeComponent();
}
private void ToggleButtonChecked(object sender, RoutedEventArgs e)
{
var button = (ToggleButton)sender;
DataGridRow dataGridRow =
(DataGridRow)dataGrid.ItemContainerGenerator.ContainerFromItem(button.DataContext);
dataGridRow.DetailsVisibility =
(button.IsChecked??false) ? Visibility.Visible : Visibility.Collapsed;
}
}
You would of course use ObservableCollection<> and implement INotifyPropertyChanged for real code.
public class DataGridRowDetails2ViewModel
{
public DataGridRowDetails2ViewModel()
{
Items = new List<DataItemWithDetails>
{
new DataItemWithDetails{ Name = "Item 1"},
new DataItemWithDetails{ Name = "Item 2"},
new DataItemWithDetails{ Name = "Item 3"},
new DataItemWithDetails{ Name = "Item 4"},
};
}
public IList<DataItemWithDetails> Items { get; set; }
public bool IsChecked { get; set; }
}
public class DataItemWithDetails
{
public DataItemWithDetails()
{
Doubles = new List<double> {1, 2, 3, 4};
}
public string Name { get; set; }
public IList<double> Doubles { get; set; }
}

You need to set AutoGenerateColumns="False" and define the columns you want yourself
Here's a quick example which will display your collection in a ListBox instead instead of the default TextBlock
<DataGrid AutoGenerateColumns="False"
ItemsSource="{Binding YourCollection}">
<DataGrid.Columns>
<DataGridTemplateColumn Header="Column Header">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ListBox ItemsSource="{Binding MySubCollection}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>

Related

WPF DataGridTextColumn direct edit

i have very simple DataGridTextColumn which should be modified on doubleclick event.
question is what should be added to avoid exception System.InvalidOperationException: ''EditItem' is not allowed for this view.'
<DataGrid x:Name="DG" ItemsSource="{Binding}" GridLinesVisibility="None" Grid.Column="3" Grid.Row="2">
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="{Binding VariantSet, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" MinWidth="60" />
</DataGrid.Columns>
</DataGrid>
simple class:
Public Class CName
Public Property Name As String = "not editable name"
End Class
on load simply added to datagrid
Private Sub MainWindow_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
Me.DG.Items.Add(New CName)
End Sub
when declared through template as following, there is no difference, same error
<DataGridTemplateColumn Header="Name" IsReadOnly="False">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<TextBox Text="{Binding Name}" />
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
even when Implements ComponentModel.INotifyPropertyChanged is added to the CName, no difference
You've not shown us enough to tell what you're doing wrong.
Here's a working window with datagrid.
The code is c# but you can run it through a converter to vb if you particularly want vb. I think it's a bad idea for a beginner to choose vb nowadays. Almost nobody publishes samples using vb.
In my mainwindow:
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
<Grid>
<DataGrid AutoGenerateColumns="False"
ItemsSource="{Binding People}">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding FirstName}" Header="First Name"/>
<DataGridTextColumn Binding="{Binding LastName}" Header="SurName"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
That viewmodel:
public class MainWindowViewModel : BaseViewModel
{
private ObservableCollection<Person> people = new ObservableCollection<Person>();
public ObservableCollection<Person> People
{
get { return people; }
set { people = value; }
}
public MainWindowViewModel()
{
People.Add(new Person { FirstName = "Chesney", LastName = "Brown" });
People.Add(new Person { FirstName = "Gary", LastName = "Windass" });
People.Add(new Person { FirstName = "Liz", LastName = "McDonald" });
People.Add(new Person { FirstName = "Carla", LastName = "Connor" });
}
}
Person just has those two properties first and last name:
public class Person : BaseViewModel
{
private string firstName;
public string FirstName
{
get { return firstName; }
set { firstName = value; RaisePropertyChanged(); }
}
private string lastName;
public string LastName
{
get { return lastName; }
set { lastName = value; RaisePropertyChanged(); }
}
That inherits from BaseViewmodel which just implements inotifypropertychanged.
public class BaseViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Here I am editing a row

Getting empty combobox dropdown in WPF

I'm trying to populate the dropdown list within the Grid column but it's empty. The Grid column is defined like this:
<DataGridTemplateColumn Header="Voucher Type" >
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding VoucherType}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox SelectedItem="{Binding VoucherType}" ItemsSource="{Binding Path=DataContext.VTypes, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"/>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
The context class has the source defined like this:
public static ObservableCollection<string> VType { get; } = new ObservableCollection<string>()
{
"Journal Voucher",
"Cash Received Voucher",
"Cash Payment Voucher",
"Bank Received Voucher",
"Bank Payment Voucher",
};
Can someone please point to what I'm doing wrong?
Thanks.
Here is simple code how to add combobox in DataGrid.
Xaml Code:
<Grid>
<DataGrid Margin="5" ItemsSource="{Binding}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="{Binding Name}"/>
<DataGridTextColumn Header="Capital" Binding="{Binding Capital}"/>
<DataGridTextColumn Header="Time Zone" Binding="{Binding TimeZone}"/>
<DataGridTemplateColumn Header="Cities" Width="*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox Margin="2" ItemsSource="{Binding Cities}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
private ObservableCollection<State> states = new ObservableCollection<State>();
public MainWindow()
{
InitializeComponent();
states.Add(new State()
{
Name = "Maryland",
Capital = "Annapolis",
TimeZone = "Eastern",
Cities = new ObservableCollection<string>() { "Frederick", "Baltimore", "Rockville"}
});
DataContext = states;
}
}
Model:
public class State
{
public string Name
{ get; set; }
public string TimeZone
{ get; set; }
public string Capital
{ get; set; }
public ObservableCollection<string> Cities
{ get; set; }
}
Hope this code may be help you.

DataGrid - Binding to a list of lists with MVVM

I have a 'Person' class that can have a list of Items.
Person.cs
public class Person : ObservableObject
{
private string _id;
public string Id
{
get { return _id; }
set
{
_id = value;
RaisePropertyChangedEvent("Id");
}
}
...
Private IList<Item> _items;
public IList<Item> Items
{
get { return _items; }
set
{
_items = value;
RaisePropertyChangedEvent("Items");
}
}
}
Item.cs
public class Item : ObservableObject
{
private string _id;
public string Id
{
get { return _id; }
set
{
_id = value;
RaisePropertyChangedEvent("Id");
}
}
...
}
I am trying to display this in a tabbed data grid. First Tab is "Person" and the second tab is "Items".
.XAML
<Window.DataContext>
<viewModels:PersonViewModel />
</Window.DataContext>
<TabControl>
<TabItem Header="Person">
<DataGrid ItemsSource="{Binding Person}" Width="1700" Height="800" AutoGenerateColumns="False" CanUserAddRows="false"
VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto" RowHeight="20">
<DataGrid.Columns>
<DataGridTextColumn Header="Id" Binding="{Binding Id}" IsReadOnly="True"/>
...
</DataGrid.Columns>
</DataGrid>
</TabItem>
<TabItem Header="Items">
<DataGrid ItemsSource="{Binding Person.Items}" Width="1700" Height="800" AutoGenerateColumns="False" CanUserAddRows="false"
VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto" RowHeight="20">
<DataGrid.Columns>
<DataGridTextColumn Header="Person Id" Binding="{Binding Person.Id}" IsReadOnly="True"/>
<DataGridTextColumn Header="Id" Binding="{Binding Id}" IsReadOnly="True"/>
...
</DataGrid.Columns>
</DataGrid>
</TabItem>
</TabControl>
View Model.cs
private ObservableCollection<Person> _persons = new ObservableCollection<Person>();
public ObservableCollection<Person> Person
{
get { return _persons; }
set
{
_persons = value;
RaisePropertyChangedEvent("Person");
}
}
This is as far as I have gotten. I'm assuming I need to somehow loop through the Person objects and then do Person.Items but am not sure.
I am able to display my "Person" object perfectly fine, it is just the "Items" tab I am having issues with. I get the error "cannot resolve property "Items" in datacontext of type ObservableCollection"
Thank you for any help
Create a view model class that keeps track of the currently selected Person as well as all people to be displayed in the first DataGrid:
public class ViewModel : ObservableObject
{
public ViewModel()
{
Persons = new ObservableCollection<Person>();
//populate your collection here...
Persons.Add(new Person() { });
//...
}
private Person _selectedPerson;
public Person SelectedPerson
{
get { return _selectedPerson; }
set
{
_selectedPerson = value;
RaisePropertyChangedEvent("SelectedPerson");
}
}
public ObservableCollection<Person> Persons { get; private set; }
}
Set the DataContext of the TabControl, or its parent window, to an instance of this view model class:
tc.DataContext = new ViewModel();
And bind to the view model properties:
<TabControl x:Name="tc">
<TabItem Header="Person">
<DataGrid ItemsSource="{Binding Persons}"
SelectedItem="{Binding SelectedPerson}"
Width="1700" Height="800" AutoGenerateColumns="False" CanUserAddRows="false"
VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto" RowHeight="20">
<DataGrid.Columns>
<DataGridTextColumn Header="Id" Binding="{Binding Id}" IsReadOnly="True"/>
</DataGrid.Columns>
</DataGrid>
</TabItem>
<TabItem Header="Items">
<DataGrid ItemsSource="{Binding SelectedPerson.Items}" Width="1700" Height="800" AutoGenerateColumns="False" CanUserAddRows="false"
VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto" RowHeight="20">
<DataGrid.Columns>
<DataGridTextColumn Header="Person Id" Binding="{Binding Person.Id}" IsReadOnly="True"/>
<DataGridTextColumn Header="Id" Binding="{Binding Id}" IsReadOnly="True"/>
</DataGrid.Columns>
</DataGrid>
</TabItem>
</TabControl>

WPF Binding a Listbox to thing that has sub enumerables

I have a class like this:
public class UIThing {
public string Name{get;set}
public IEnumerable<Thing> LotsOfThings;
}
I have some of these in a List (List<UIThings>) and I would like to bind them into a ListBox so that the LotsOfThings member is expanded as items in the ListBox. Like a list of lists I guess. But I can't get my head around the DataTemplate needed.
Any Ideas?
This may give you the idea to do it:
I recommend you to use ItemsControl
public class UIThing
{
public string Name { get; set; }
public List<string> LotsOfThings { get; set; }
}
private void Button_Click(object sender, RoutedEventArgs e)
{
UIThing item1 = new UIThing() { Name = "A", LotsOfThings = new List<string>() { "1A", "2A", "3A", "4A" } };
UIThing item2 = new UIThing() { Name = "B", LotsOfThings = new List<string>() { "1B", "2B", "3B", "4B" } };
UIThing item3 = new UIThing() { Name = "C", LotsOfThings = new List<string>() { "1C", "2C", "3C", "4C" } };
UIThing item4 = new UIThing() { Name = "D", LotsOfThings = new List<string>() { "1D", "2D", "3D", "4D" } };
var list = new List<UIThing>() { item1, item2, item3, item4 };
itemsControl.ItemsSource = list;
}
And this is the XAML:
<ItemsControl Name="itemsControl" HorizontalAlignment="Left" Width="100">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Expander Header="{Binding Name}" Margin="0,5" Width="auto">
<ListBox Width="auto" ItemsSource="{Binding LotsOfThings}" Margin="20,0,0,0" Background="AliceBlue"></ListBox>
</Expander>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Result:
EDIT: here is the ListBox version
<ListBox Name="itemsControl" Margin="0,0,197,0">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Label Content="{Binding Name}" Margin="0,5"></Label>
<Border Margin="20,0,0,0" Background="AliceBlue" CornerRadius="10">
<ListBox Width="auto" ItemsSource="{Binding LotsOfThings}" ></ListBox>
</Border>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Can use a GridView can ComboBox
<GridViewColumn Width="100">
<GridViewColumnHeader Content="Main"/>
<GridViewColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding Path=GroupsMain, Mode=OneWay}" DisplayMemberPath="Name" IsEditable="False" SelectedIndex="0" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
If you need everything in a single column then a TreeView as suggested byt Matt
You'll need a DataTemplate for your UIThing. In that template you will have another ListView that is bound to your LotsOfThings list. Then you can add a DataTemplate for your Thing class too.

Why does WPF Datagrid SelectionUnit="Cell" cause disabling of DataGridTemplateColumn controls?

This seems a bit bizzare: I have a Datagrid with a button column which deletes the row when the button for that row is clicked. BUT if I set the Datagrid SelectionUnit="Cell" then the button column is disabled and I can no longer click the button.
Can anyone tell me why this happens and how to avoid the disabling behaviour for the column?
Here is XAML that recreates the issue - add & remove the SelectionUnit="Cell" to see the change in behaviour
<Window.Resources>
<local:DummyCollection x:Key="stringCollection">
<local:Dummy x="1" y="2" z="3" />
<local:Dummy x="4" y="5" z="6" />
<local:Dummy x="7" y="8" z="9" />
</local:DummyCollection>
</Window.Resources>
<StackPanel>
<DataGrid ItemsSource="{Binding Source={StaticResource stringCollection}}" AutoGenerateColumns="False" SelectionUnit="Cell">
<DataGrid.Columns>
<DataGridTextColumn x:Name="a" Binding="{Binding Path=x}" Header="a" />
<DataGridTextColumn x:Name="b" Binding="{Binding Path=y}" Header="b" />
<DataGridTextColumn x:Name="c" Binding="{Binding Path=z}" Header="c" />
<DataGridTemplateColumn x:Name="deleteButtonColumn" Header="D">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Command="Delete" >D</Button>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</StackPanel>
Add this to the Window's code behind:
public class Dummy
{
public string x { get; set; }
public string y { get; set; }
public string z { get; set; }
}
public class DummyCollection : ObservableCollection<Dummy> { }
I don't see a difference between setting SelectionUnit="Cell" or SelectionUnit="FullRow". Neither work because the command property isn't binding to a command.
You should be binding the Command property of your button to an implementation of ICommand.
Here's an example based on your code using MVVM Light's RelayCommand.
<Window.DataContext>
<local:ViewModel/>
</Window.DataContext>
<StackPanel>
<DataGrid ItemsSource="{Binding Items}"
AutoGenerateColumns="False" SelectionUnit="Cell">
<DataGrid.Columns>
<DataGridTextColumn x:Name="a" Binding="{Binding Path=x}" Header="a" />
<DataGridTextColumn x:Name="b" Binding="{Binding Path=y}" Header="b" />
<DataGridTextColumn x:Name="c" Binding="{Binding Path=z}" Header="c" />
<DataGridTemplateColumn x:Name="deleteButtonColumn" Header="D">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Command="{Binding Path=DataContext.Delete,
RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType={x:Type DataGrid}}}"
CommandParameter="{Binding}">D</Button>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</StackPanel>
public class Dummy
{
public string x { get; set; }
public string y { get; set; }
public string z { get; set; }
public override string ToString()
{
return string.Format("x:{0}, y:{1}, z:{2}", x, y, z);
}
}
public class DummyCollection : ObservableCollection<Dummy> { }
public class ViewModel
{
public ViewModel()
{
Items = new DummyCollection
{
new Dummy {x = "1", y = "2", z = "3"},
new Dummy {x = "4", y = "5", z = "6"},
new Dummy {x = "7", y = "8", z = "9"},
};
Delete = new RelayCommand<Dummy>(DeleteItem);
}
public ICommand Delete { get; private set; }
private void DeleteItem(Dummy item)
{
Debug.WriteLine("Delete item, " + item);
}
public DummyCollection Items { get; private set; }
}

Resources