Getting empty combobox dropdown in WPF - 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.

Related

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 MVVM Databinding Nested Datagrid

I'm a beginner to WPF and MVVM pattern. I still have a issue with databinding in case of backward binding. I would like to bind selectedValues of the Comboboxes into my Oberservable Collection. I learned is possible to bind the value of a combobox to a property, but in this case I would like to bind to a property of a collection and the collection is the parent.
Could somebody explain me how to bind the combobox selectedvalues to my observablecollection?
I have a workaround in my mind to make for each combobox a new property in my viewmodel and collect all values and store by button press this values into my collection, but this seems for me wrong, because is not the behavouir of databinding.
EDIT The comboboxes items are corrected populated from each model, but how i can bind the selectedvalue of the combobox to my collection property?
<ComboBox ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type Window}},
Path=DataContext.Cities}"
DisplayMemberPath="Name"
SelectedValue="{Binding Path=RowEntries }"/>
The SelectedValue="{Binding Path=RowEntries}" is wrong or is this correct?
EDIT 2
I added a Listview binded to my collection to see, if the properties are binded to the selectedvalue of my combobox, but is keeps empty.
<ListView ItemsSource="{Binding RowEntries}" BorderBrush="Black" BorderThickness="1">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=CityName}"></TextBlock>
<TextBlock Text="{Binding Path=CountryName}"></TextBlock>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
I have this solution:
My Models:
// INPC implemented by Propertychanged.Fody
public class RowEntry : BaseModel
{
public string CityName { get; set; }
public string CountryName { get; set; }
}
// INPC implemented by Propertychanged.Fody
public class City : BaseModel
{
public string Name { get; set; }
}
// INPC implemented by Propertychanged.Fody
public class Country : BaseModel
{
public string Name { get; set; }
}
My ViewModel:
public class TestViewModel : ViewModelBase
{
#region properties
// INPC implemented by Propertychanged.Fody
public ObservableCollection<City> Cities { get; set; } = new ObservableCollection<City>();
public ObservableCollection<Country> Countries { get; set; } = new ObservableCollection<Country>();
public ObservableCollection<RowEntry> RowEntries { get; set; } = new ObservableCollection<RowEntry>();
#endregion
#region constructors and destructors
/// <summary>
/// Initializes a new instance of the MainViewModel class.
/// </summary>
public TestViewModel()
{
// Sample Data
var temp = new City { Name = "Rom" };
Cities.Add(temp);
var temp2 = new City { Name = "Sydney" };
Cities.Add(temp2);
var temp3 = new Country { Name = "Italy" };
Countries.Add(temp3);
var temp4 = new Country { Name = "Australia" };
Countries.Add(temp4);
RowEntries.Add(new RowEntry());
}
#endregion
}
My Ui:
<StackPanel>
<DataGrid ItemsSource="{Binding RowEntries}" AlternationCount="{Binding Items.Count, RelativeSource={RelativeSource Self}}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding AlternationIndex, RelativeSource={RelativeSource AncestorType=DataGridRow}}" Header="#"/>
<DataGridTemplateColumn Header="City">
<DataGridTemplateColumn.CellTemplate>
<HierarchicalDataTemplate>
<ComboBox ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type Window}},
Path=DataContext.Cities}"
DisplayMemberPath="Name"
SelectedValue="{Binding Path=RowEntries }"/>
</HierarchicalDataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Countries">
<DataGridTemplateColumn.CellTemplate>
<HierarchicalDataTemplate>
<ComboBox ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type Window}},
Path=DataContext.Countries}"
DisplayMemberPath="Name"/>
</HierarchicalDataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
<Button Content="Add Row" Margin="0,0,1797,0"></Button>
</StackPanel>
You should bind the SelectedValue property of the ComboBoxes to the CityName and CountryName properties of the RowEntry object and set the SelectedValuePath property of the ComboBoxes to "Name". Also set the UpdateSourcePropertyTrigger of the bindings to PropertyChanged:
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding AlternationIndex, RelativeSource={RelativeSource AncestorType=DataGridRow}}" Header="#"/>
<DataGridTemplateColumn Header="City">
<DataGridTemplateColumn.CellTemplate>
<HierarchicalDataTemplate>
<ComboBox ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.Cities}"
DisplayMemberPath="Name"
SelectedValuePath="Name"
SelectedValue="{Binding Path=CityName, UpdateSourceTrigger=PropertyChanged}"/>
</HierarchicalDataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Countries">
<DataGridTemplateColumn.CellTemplate>
<HierarchicalDataTemplate>
<ComboBox ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.Countries}"
DisplayMemberPath="Name"
SelectedValuePath="Name"
SelectedValue="{Binding Path=CountryName, UpdateSourceTrigger=PropertyChanged}"/>
</HierarchicalDataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
Then the setters of the source properties (CityName and CountryName) will be called when you select an item in the ComboBoxes.
If you want to select some values initially, you simply set these properties to some values that are present in the ComboBoxes:
public TestViewModel()
{
...
RowEntries.Add(new RowEntry() { CityName = "Sydney", CountryName = "Australia" });
}

WPF DataGrid: Bind columns to different levels of the source collection

How to bind a WPF DataGrid to get the following result: http://i.stack.imgur.com/7Wne0.png
Note the desired +/- buttons on the left.
The DataGrid's ItemsSource is bound to an IEnumerable of Document.
The question is how to define the columns so they bind to the second and third level of objects and get the expand/collapse buttons.
I don't want to have a second and a third grids for Group and Field items, defined in the RowDetail of the parent.
The goal is to have a single DataGrid, one row of columns' headers and the ability to expand the child elements of the current row (if any).
public class Document
{
public string Name { get; set; }
public IEnumerable<Group> Groups { get; set; }
}
public class Group
{
public string Name { get; set; }
public IEnumerable<Field> Fields { get; set; }
}
public class Field
{
public string Name { get; set; }
public FieldType FieldType { get; set; }
public bool IsRequired { get; set; }
}
Just bind to the property i.e. {Binding Document.Group.Name}.
Note that all your classes must implement INotifyPropertyChanged. All your properties should raise PropertyChanged as is widely documented.
Your collections should be ObservableCollections.
The only trick i found here is to use nested datagrids with template columns and no headers for smooth view.
In Code:
DocumentGrid.ItemsSource = YourDocument;
The XAML:
<DataGrid x:Name="DocumentGrid" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Name}" Header="Document"/>
<DataGridTemplateColumn Header="Group Field FieldType IsRequired">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<DataGrid x:Name="dgInner" ItemsSource="{Binding Groups}" AutoGenerateColumns="False" GridLinesVisibility="None" BorderThickness="0" HeadersVisibility="None">
<DataGrid.Columns>
<DataGridTextColumn Header="Group" Binding="{Binding Name}"/>
<DataGridTemplateColumn Header="Field">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<DataGrid x:Name="dgInnerInner" ItemsSource="{Binding Fields}" AutoGenerateColumns="False" GridLinesVisibility="None" BorderThickness="0" HeadersVisibility="None">
<DataGrid.Columns>
<DataGridTextColumn Header="Field" Binding="{Binding Name}"/>
<DataGridTextColumn Header="FieldType" Binding="{Binding FieldType}"/>
<DataGridTextColumn Header="IsRequired" Binding="{Binding IsRequired}"/>
</DataGrid.Columns>
</DataGrid>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
EDIT
If you want +- buttons to collapse details you should use DataGrid's RowDetailsTemplate like described here

Accessing data from Datagrid with template columns

I have created a datagrid with DataGridTemplateColumns. I want to access the values of datagrid cells.
Here is my Xaml code:
<DataGrid x:Name="dgMappingColumns" Height="180" Width="450" SelectionMode="Extended" SelectionUnit="FullRow" ItemsSource="{Binding}" AutoGenerateColumns="False" CanUserAddRows="False" PreparingCellForEdit="dgMappingColumns_PreparingCellForEdit">
<DataGrid.Columns>
<DataGridTemplateColumn Header="CSVColumn">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox x:Name="txtbDataColumn" Width="140" Text="{Binding DataColumn}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="ExcelColumn">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox x:Name="cmbExcelColumn"
ItemsSource="{Binding ExcelColumn}"
Width="220"
SelectedItem="ExcelColumn"
SelectionChanged="cmbExcelColumn_SelectionChanged"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Required">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox x:Name="cmbRequired"
ItemsSource=
"{Binding Required}"
SelectedItem="Required"
SelectionChanged="cmbRequired_SelectionChanged"
/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
and here is the code behind that I have tried to access value of each cell from datagrid.
private void btnSave_Click(object sender, RoutedEventArgs e)
{
for (int i = 0; i < dgMappingColumns.Items.Count; i++)
{
TextBlock a = dgMappingColumns.Columns[0].GetCellContent(dgMappingColumns.Items[i]) as TextBlock;
string aa = a.Text;
}
Also I am binding list of the following class to datagrid for generating rows.
i.e.public List<DataGridRowSet> DataGridRowSets { get; set; }
public class DataGridRowSet
{
public string DataColumn { get; set; }
public ObservableCollection<string> ExcelColumn { get; set; }
public ObservableCollection<string> Required { get; set; }
}

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