Datagrid column headers and values binding - c#

I'm having a problem with binding columns headers and values to data grid in MVVM PRISM.
I'm having class SearchResult that has two properties :
ColumnNames ---> those will apply to headers
ColumnValues ---> those will apply to specific headers as their values.
public class SearchResult
{
public List ColumnNames;
public List<object> ColumnValues;
public string ColumnName { get; set; }
public object ColumnValue { get; set; }
public SearchResult()
{
this.ColumnNames = new List<string>();
this.ColumnValues = new List<object>();
}
public void AddColumnAttributes(string columnName, object columnValue)
{
this.ColumnNames.Add(columnName);
this.ColumnValues.Add(columnValue);
this.ColumnName = columnName;
this.ColumnValue = columnValue;
}
}
those properties are lists that are populated dynamically through C# code.
And I need to bind them to columns headers and columns values in XAML.
I've already created ObservableCollection Result which is the source of my gridview, but still is doesn't bind headers and values.
public ObservableCollection<SearchResult> Result
{
get { return this.searchResult; }
set
{
this.searchResult = value;
this.NotifyPropertyChanged("SearchResult");
}
}
And XAML:
<DataGrid ItemsSource="{Binding SearchResult}" Width="350">
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.Header>
<TextBlock Text="{Binding ColumnName, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}"></TextBlock>
</DataGridTemplateColumn.Header>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
I'll appreciate any help with this!!!

I thick your code should be like this..
<DataGrid ItemsSource="{Binding SearchResult, ElementName=PageTitle}" Width="350">
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.Header>
<TextBlock Text="{Binding ColumnNames/ColumnName, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}"></TextBlock>
</DataGridTemplateColumn.Header>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>

Related

Property setter in binding called twice

In order to try to be generic I implemented that WPF and C# Binding in the view but the problem is that the setter of the Value property in the Item class is called two times successively (without passing in any other function - I checked the call stack)
I don't really know if it's a problem of binding or a problem in the code but If you have any idea I would like to hear your feedback.
<DataGrid Grid.Row="1" ItemsSource="{Binding Questions}" AutoGenerateColumns="False" SelectedItem="{Binding Path=DataContext.Answer.QuestionItem, RelativeSource={RelativeSource AncestorType=UserControl}}" >
<DataGrid.Columns>
<DataGridTemplateColumn Header="Questions" Width="SizeTocells" IsReadOnly="True">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Question}"></TextBlock>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Answers" Width="SizeToCells" IsReadOnly="True">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" >
<ListView ItemsSource="{Binding Path=DataContext.Answer.Answers, Mode=OneWay, RelativeSource={RelativeSource AncestorType=UserControl}}">
<ListView.ItemTemplate>
<DataTemplate>
<RadioButton GroupName="{Binding Path=GroupName, Mode=OneWay}" Content="{Binding Path=Content, Mode=OneWay}" IsChecked="{Binding Path=Value, Mode=TwoWay}" Margin="0, 0, 10, 0" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
public class Item : BindableBase
{
public string Content { get; }
public string GroupName { get; }
private bool _val;
public bool Value
{
get { return _val; }
set
{
SetProperty(ref _val, value);
}
}
public Item(string content, string groupName, bool value = false)
{
Content = content;
GroupName = groupName;
Value = value;
}
}
public class AnswerModel
{
public List<Item> Answers { get; }
public AnswerModel(List<string> possibleAnswers)
{
Answers = new List<Item>();
for (int i = 0; i < possibleAnswers.Count(); ++i)
{
char c = Convert.ToChar('A' + i);
var letter = Convert.ToString(c);
Answers.Add(new Item(letter, "group" + letter));
}
}
}
public class InsertWordQuestionViewModel : BindableBase, INavigationAware
{
public AnswerModel Answer { get; private set; }
public void OnNavigatedTo(NavigationContext navigationContext)
{
PossibleAnswers = new List<string>() { "A test", "B test2", "C test3"};
Questions = navigationContext.Parameters["questions"] as List<QuestionModel<string, string>>;
Answer = new AnswerModel(PossibleAnswers);
}
}
Value property in the Item class is called two times successively - I guess it happens in different items: one item/RadioButton gets unchecked, and another item/RadioButton gets checked. so value argument should be different for those two consequtive invocations

Bind a DataGrid to the selectedrow object of a second datagrid, WPF Caliburn.Micro

I have a datagrid that is bound to a BindableCollection, this is working properly, every repair order I add to the BindableCollection shows in the Datagrid.
I have a second Datagrid on the same view for "WriteOffs", Each "RepairOrder" has a property of BindableCollection.
What I am trying to do is bind the WriteOff DataGrid to the WriteOffs of the selected row. So every time the user selects a row in the "RepairOrder" datagrid the write offs stored in the writeoff property is shown in the WriteOff datagrid.
What is the best way to handle this?
RepairOrder class:
public string ControlNumber { get; set; }
public double Value { get; set; }
public string Note { get; set; }
public string Schedule { get; set; }
public int Age { get; set; }
public List<WriteOff> WriteOffs { get; set; }
public RepairOrder(string CN, string SC, double VL)
{
ControlNumber = CN;
Schedule = SC;
Value = Math.Round(VL,2);
Note = null;
WriteOffs = new List<WriteOff>();
}
public RepairOrder()
{
}
public void AddWriteOff(WriteOff WO)
{
WriteOffs.Add(WO);
}
public BindableCollection<WriteOff> GetWriteOffs()
{
BindableCollection<WriteOff> temp = new BindableCollection<WriteOff>();
foreach (var item in WriteOffs)
{
temp.Add(item);
}
return temp;
}
public static RepairOrder FromCSV(string CSVLine, string Sched)
{
string[] values = CSVLine.Split(',');
RepairOrder rep = new RepairOrder();
rep.ControlNumber = values[2];
rep.Value = Math.Round(double.Parse(values[5]),2);
rep.Age = int.Parse(values[4]);
rep.Schedule = Sched;
return rep;
}
The XML for the Data grid showing the repair orders:
<Border BorderBrush="Black" BorderThickness="2" CornerRadius="5" Grid.Column="1" Grid.Row="1">
<DataGrid x:Name="ScheduleGrid" ItemsSource="{Binding RepairOrders}" CanUserSortColumns="True" AutoGenerateColumns="False" SelectedIndex="{Binding SelectedRepairOrder}" SelectionMode="Single">
<DataGrid.Columns>
<DataGridTextColumn Header="Schedule" Binding="{Binding Schedule}" Width="75" IsReadOnly="True"/>
<DataGridTextColumn Header="Control Number" Binding="{Binding ControlNumber}" Width="110" IsReadOnly="True"/>
<DataGridTextColumn Header="Age" Binding="{Binding Age}" Width="50" IsReadOnly="True"/>
<DataGridTextColumn Header="Value" Binding="{Binding Value, StringFormat=C}" Width="75" IsReadOnly="True"/>
<DataGridTextColumn Header="Note" Binding="{Binding Note}" Width="*"/>
</DataGrid.Columns>
</DataGrid>
</Border>
XML for the Data grid for the write-offs:
<Border Grid.Column="1" Grid.Row="0" Grid.RowSpan="2" BorderBrush="Black" BorderThickness="2" CornerRadius="5" Margin="5,2,5,5">
<StackPanel Orientation="Vertical">
<TextBlock Text="Write Off List" HorizontalAlignment="Center" FontSize="20"/>
<DataGrid ItemsSource="{Binding WriteOffs}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Account" Binding="{Binding Account}" Width="100" IsReadOnly="True"/>
<DataGridTextColumn Header="Description" Binding="{Binding Description}" Width="200" IsReadOnly="True"/>
<DataGridTextColumn Header="Amount" Binding="{Binding WriteOffAmount}" Width="*" IsReadOnly="True"/>
</DataGrid.Columns>
</DataGrid>
</StackPanel>
</Border>
I was thinking of making an event when the user selects a row, but I can't seem to find a way to get the value if the selected row into the ViewModel method.
I can't seem to find a clear tutorial or post on exactly how to hand this situation.
What is the easiest way to accomplish my final goals?
Okay this is the way I would do...
Instead of SelectedIndex on your ScheduleGrid DataGrid you need to use SelectedItem.
So your XAML would look like this:
<DataGrid x:Name="ScheduleGrid" ItemsSource="{Binding RepairOrders}" SelectedItem="{Binding SelectedRepairOrder} ...."
Not to the ViewModel
Now you need to make the SelectedItem property, or SelectedRepairOrder.
The property should look like this:
private RepairOrder _selectedRepairOrder;
public RepairOrder SelectedRepairOrder
{
get { return _selectedRepairOrder; }
set
{
if (_selectedRepairOrder == value) return;
_selectedRepairOrder = value;
NotifyOfPropertyChange(() => SelectedRepairOrder);
NotifyOfPropertyChange(() => WriteOffsCollection);
}
}
Second, since we have two DataGrids we need also two Collections.
The ScheduleGrid should have a Collection who looks like this:
private BindableCollection<RepairOrder> _repiarOrdersCollection;
public BindableCollection<RepairOrder> RepairOrders
{
get { return _repiarOrdersCollection; }
set
{
_repiarOrdersCollection = value;
}
}
And the WriteOffs DataGrid collection should be like this:
public BindableCollection<WriteOff> WriteOffs
{
get
{
return GetWriteOffs();
}
}
Okay... now what happens... As you can see in your SelectedRepairOrder property, after it changes, it will notify your WriteOffs collection that it has changed. And since we are newer setting the value of its DataGrid, we don't need any setter.
Now one thing is missing. Since you have two collections, I believe that you want after selecting item from one collection to filter the items on other collection? Right? If yes, the you need to extend your GetWriteOffs() method, to have parameters of some king, string, int... and inside it filter your data.

How to bind a ObservableCollection of type string to Datagrid

I have a rather simple query, I have a ObservableCollection of type string called Complaints. Now I would like to bind this ObservableCollection to a DataGrid with two columns: a checkbox template column, and Complaint column which contains the complaint. I would want to know how to achieve this? I'm using MVVM. I have set the ItemsSource property of the DataGrid to Complaints but cannot see the data as I do not know what to put in Binding for the second column of the DataGrid. How do I do this?
My View Model
public class MyViewModel() : INotifyPropertyChanged
{
private ObservableCollection<string> _complaints;
public ObservableCollection<string> Complaints
{
get
{
return _complaints;
}
set
{
_complaints = value;
NotifyPropertyChanged("Complaints");
}
}
}
My Datagrid
<DataGrid x:Name="dg_pc" ItemsSource="{Binding Path=Complaints}" Grid.Column="0">
<DataGrid.Columns>
//Checkbox Template Column.
<DataGridTemplateColumn Width="6.5217*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox x:Name="cb_datagrid" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
//This is the column, where I need to display the **Complaint** Collection
<DataGridTextColumn Width="93.4782*"Header="Complaints" />
</DataGrid.Columns>
</DataGrid>
You need to set the Binding property of the DataGridTextColumn. This binding is relative to the data items of the source collection. Since your source collection itself is the item you want to display in the column, the binding will be to the item itself.
<DataGridTextColumn Width="93.4782*" Header="Complaints" Binding="{Binding}"/>
or equivalently:
<DataGridTextColumn Width="93.4782*" Header="Complaints" Binding="{Binding Path=.}"/>
Typically for a DataGrid you would be binding to a collection of objects with various properties you want to display each in a column, not just a collection of strings.
Model:
public class Complaint : INotifyPropertyChanged
{
public bool IsActive { get; set; }
public string Content { get; set; }
...
}
View Model:
public class ComplaintsViewModel
{
public ObservableCollection<Complaint> Complaints { get; set; }
...
}
View:
<DataGrid ItemsSource="{Binding Complaints}">
<DataGrid.Columns>
<DataGridCheckBoxColumn Header="Active" Binding="{Binding IsActive}"/>
<DataGridTextColumn Header="Content" Binding="{Binding Content}"/>
</DataGrid.Columns>
<DataGrid/>

Combobox(inside a DataGrid) Binding to Observable List not loading

I have a ComboBox column inside a DataGrid. I need it to display values based on another WPF control, not in DataGrid. So, the values of ComboBox should change according to that control. I have created an ObservableCollection and binded it to the ComboBox but it does not display any values. For displaying dynamically, I have added DropOpenOpened event. But the ComboBox does not display any value. The list that populates the ComboBox is getting updated but its not displaying anything.
Below is the xaml code.
The DataGrid is bound to another List, whose values I fetch from DB.
<DataGrid x:Name="grid1" AutoGenerateColumns="False" Grid.Row="1" Grid.RowSpan="2" Grid.Column="1" Grid.ColumnSpan="2"
AlternatingRowBackground="Azure" AlternationCount="2" CanUserReorderColumns="True" CanUserResizeRows="True" CanUserSortColumns="True"
DataContext="attr">
<DataGrid.Columns>
<DataGridTextColumn Width="*" Binding="{Binding modify_user}" Header="Modified By" IsReadOnly="True"></DataGridTextColumn>
<DataGridTextColumn Width="*" Binding="{Binding modify_date}" Header="Modified Date" IsReadOnly="True"></DataGridTextColumn>
<DataGridTemplateColumn Header="Source" Width="*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding source_value}"></TextBlock>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox x:Name="combo_source" ItemsSource="{Binding Path=sourceComboDropdown}"
DisplayMemberPath="desc" SelectedValuePath="id"
SelectedItem="{Binding source_value}"
SelectedValue="{Binding Path=source_value,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
DropDownOpened="combo_source_DropDownOpened"/>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Target value" Width="*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding target_value}"></TextBlock>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding targetComboDropdown}"
DisplayMemberPath="desc" SelectedValuePath="id"
SelectedItem="{Binding target_value}"
SelectedValue="{Binding Path=target_value,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
DropDownOpened="ComboBox_DropDownOpened"/>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
The class of observablecollection is:
public static ObservableCollection<Source> sourceComboDropdown =
new ObservableCollection<Source>();
public static ObservableCollection<Source> targetComboDropdown =
new ObservableCollection<Source>();
public class Source
{
public string id { get; set; }
public string desc { get; set; }
}
Where desc is the DisplayMember value and id is SelectedValue.
The class for DataGrid list is:
public static ObservableCollection<Attribute_Param> attr =
new ObservableCollection<Attribute_Param>();
public class Attribute_Param
{
public string source_value { get; set; }
public string target_value { get; set; }
public string modify_user { get; set; }
public DateTime modify_date { get; set; }
}
I have tried adding static resource. But since I need to dynamically update the values, I couldn't figure out a work around to use it.
I think I am missing something really small but cant figure out what.
I think it is unable to find the list sourceComboDropdown since the dataContext of the DataGrid is attr which does not CONTAIN sourceCombo drop down.
I would set my datagrid datacontext to the whole viewmodel. i.e DataGrid.DataContext=model in the constructor of the code behind.
Then I can bind the datagrid to attr using : ItemsSource="{Binding attr}" inside the DataGrid tag . Assuming attr is immediately in the VieWModel model. That way it should be able to detect the combo box items soruce which is now in the data context i.e model contains sourceDropDown .
If that doesnt work , try this :
<DataGridTemplateColumn >
<DataGridTemplateColumn.CellTemplate>
<DataTemplate >
<ComboBox Loaded="LoadItemsSource"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
private void LoadItemsSource(object sender, RoutedEventArgs e)
{
ComboBox comboBox = sender as ComboBox;
comboBox.ItemsSource=model.sourceDropDown;
}

wpf datagrid: Make cell Editable and non-Editable

I have a datagrid with 2 columns one is DataGridComboBox namely 'Serial No' column and other is DataGridTextColumn namely 'Qty' .The DataGridComboBox column may or not having values in it.If the combobox has values then the user can select one value from it then the qty column become uneditable (readonly) and set 1 as default qty other wise it become editable cell ,Hence the user can enter any qty on it.How do I made the Qty column editable and non editable based on the value selection from combobox ?
you can try some thing like this
<Grid>
<DataGrid ItemsSource="{Binding A}" AutoGenerateColumns="False" >
<DataGrid.Columns>
<DataGridTemplateColumn Header="Choose" >
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding ListOfValues}" SelectedValue="{Binding Selected, UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Value">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox Text="{Binding Selected, UpdateSourceTrigger=PropertyChanged}" IsReadOnly="{Binding ValueAvalible}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Grid
In the Cell Demplate as of now i have put only TextBox and bound ValueAvalible to readonly Property. You can also have both label and TextBox and you can bind ValueAvalible to visiblity of label and TextBox and. You can hide the text show the label if value is available. If no value is there in the comboBox you can hide label and show the textBox which will give a better user experience like below
<StackPanel>
<Label Content="{Binding Selected}" Visibility="{Binding ValueAvalible, Converter={StaticResource ResourceKey=booleanToVisiblity}}"/>
<TextBox Text="{Binding Selected, UpdateSourceTrigger=PropertyChanged}" Visibility="{Binding ValueAvalible, Converter={StaticResource ResourceKey=invertedBooleanToVisiblity}}"/>
</StackPanel>
In your viewModel
class ViewModel
{
public ObservableCollection<Values> A
{
get;
set;
}
public ViewModel()
{
A = new ObservableCollection<Values>();
Values vv = new Values();
vv.ListOfValues = new ObservableCollection<string>();
vv.ListOfValues.Add("1");
vv.ListOfValues.Add("2");
vv.Selected = vv.ListOfValues[0];
vv.ValueAvalible = true;
A.Add(vv);
A.Add(new Values());
}
}
public class Values : NotifiyPropertyChanged
{
public ObservableCollection<string> ListOfValues
{
get;
set;
}
private string selectedValue;
public string Selected
{
get
{
return selectedValue;
}
set
{
selectedValue = value;
Notify("Selected");
}
}
public bool ValueAvalible
{
get;
set;
}
}
public class NotifiyPropertyChanged : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void Notify(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}

Categories