Datagrid binding in WPF - c#

I know this has been asked already but I have done almost everything what is suggested by developers.
<DataGrid x:Name="Imported" VerticalAlignment="Top"
DataContext="{Binding Source=list}"
AutoGenerateColumns="False" CanUserResizeColumns="True">
<DataGrid.Columns>
<DataGridTextColumn Header="ID" Binding="{Binding Path=ID}"/>
<DataGridTextColumn Header="Date" Binding="{Binding Path=Date}"/>
</DataGrid.Columns>
</DataGrid>
I am trying to show this in modal dialog box and populating the license list in the constructor of the modal dialog box.
But still nothing is getting populated inside the DataGrid.
Constructor code:
public diagboxclass()
{
List<object> list = new List<object>();
list = GetObjectList();
}
public class object
{
string id;
DateTime date;
public string ID
{
get { return id; }
set { id = value; }
}
public DateTime Date
{
get { return date; }
set { date = value; }
}
}
Do you guys think something to do with the object list?

PLEASE do not use object as a class name:
public class MyObject //better to choose an appropriate name
{
string id;
DateTime date;
public string ID
{
get { return id; }
set { id = value; }
}
public DateTime Date
{
get { return date; }
set { date = value; }
}
}
You should implement INotifyPropertyChanged for this class and of course call it on the Property setter. Otherwise changes are not reflected in your ui.
Your Viewmodel class/ dialogbox class should have a Property of your MyObject list. ObservableCollection<MyObject> is the way to go:
public ObservableCollection<MyObject> MyList
{
get...
set...
}
In your xaml you should set the Itemssource to your collection of MyObject. (the Datacontext have to be your dialogbox class!)
<DataGrid ItemsSource="{Binding Source=MyList}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="ID" Binding="{Binding ID}"/>
<DataGridTextColumn Header="Date" Binding="{Binding Date}"/>
</DataGrid.Columns>
</DataGrid>

Without seeing said object list, I believe you should be binding to the DataGrid's ItemsSource property, not its DataContext.
<DataGrid x:Name="Imported" VerticalAlignment="Top"
ItemsSource="{Binding Source=list}"
AutoGenerateColumns="False" CanUserResizeColumns="True">
<DataGrid.Columns>
<DataGridTextColumn Header="ID" Binding="{Binding ID}"/>
<DataGridTextColumn Header="Date" Binding="{Binding Date}"/>
</DataGrid.Columns>
</DataGrid>
(This assumes that the element [UserControl, etc.] that contains the DataGrid has its DataContext bound to an object that contains the list collection. The DataGrid is derived from ItemsControl, which relies on its ItemsSource property to define the collection it binds its rows to. Hence, if list isn't a property of an object bound to your control's DataContext, you might need to set both DataContext={Binding list} and ItemsSource={Binding list} on the DataGrid).

Try to do this in the behind code:
public diagboxclass()
{
List<object> list = new List<object>();
list = GetObjectList();
Imported.ItemsSource = null;
Imported.ItemsSource = list;
}
Also be sure your list is effectively populated and as mentioned by #Blindmeis, never use words that already are given a function in C#.

Related

Populating a ComboBox from ViewModel or Model in a datagrid

Hello I have a data grid here:
<DataGrid CanUserResizeRows="False" VerticalAlignment="Top" Margin="0,10,80,0" Grid.Column="0" Grid.RowSpan="3" Grid.ColumnSpan="3" HorizontalAlignment="Center" Grid.Row="1" CanUserAddRows="False" AutoGenerateColumns="False" HeadersVisibility="All">
<DataGrid.Columns>
<!-- Combobox-->
<materialDesign:DataGridComboBoxColumn HeaderStyle="{StaticResource DGHeader}" Header="Part Number" ItemsSource="{x:Static model:ViewModel.EpicorParts}"/>
<!--TextBox Column-->
<materialDesign:DataGridTextColumn HeaderStyle="{StaticResource DGHeader}" Header="Description" Binding="{Binding Description}" ElementStyle="{StaticResource MaterialDesignDataGridTextColumnStyle}" EditingElementStyle="{StaticResource MaterialDesignDataGridTextColumnPopupEditingStyle}"/>
<!--Combobox Column-->
<materialDesign:DataGridComboBoxColumn HeaderStyle="{StaticResource DGHeader}" Header="Reason Code"/>
<materialDesign:DataGridComboBoxColumn HeaderStyle="{StaticResource DGHeader}" Header="Qty"/>
<!--Numeric Column -->
<DataGridTemplateColumn Header="BOM" HeaderStyle="{StaticResource DGHeader}" CellTemplate="{StaticResource ButtonColumn}"/>
</DataGrid.Columns>
</DataGrid>
So different ObservableCollections populate different areas on here but to start I have a model:
public EpicorParts(SqlDataReader reader)
{
Part = reader.GetString("PartNum");
Desc = reader.GetString("PartDescription");
partClass = reader.GetString("ClassID");
}
private string _Part;
public string Part
{
get { return _Part; }
set { _Part = value; RaisePropertyChanged("PartNum"); }
}
private string _Desc;
public string Desc
{
get { return _Desc; }
set { _Desc = value; RaisePropertyChanged("PartDescription"); }
}
private string _partClass;
public string partClass
{
get { return _partClass; }
set { _partClass = value; RaisePropertyChanged("ClassID"); }
}
I would like to populate the part number column with PartNum and the Description column with Desc I've tried using the item source as you see in the combobox column but it doesn't let me nest anymore. Will not let me do ItemsSource="{x:Static model:ViewModel.EpicorParts.Part}"
Just in case this is needed here is the viewmodel
public static ObservableCollection<EpicorParts> EpicorParts { get; set; } = new ObservableCollection<EpicorParts>();
public static void GetEpicorParts()
{
EpicorParts.Clear();
using var conn = new SqlConnection(Settings.Default.Epicor2Connection);
conn.Open();
string qry = "SELECT PartNum, PartDescription, ClassID from Erp.part where ClassID='slnc' and InActive = 1";
var cmd = new SqlCommand(qry, conn);
var reader = cmd.ExecuteReader();
while (reader.Read())
{
EpicorParts.Add(new EpicorParts(reader));
}
conn.Close();
}
Hopefully I made this clear of what I am trying to do.
First, there are some issues with your viewmodel, I believe. You're triggering property change with "PartNum" for two properties. Pay attention to your properties:
public class EpicorPart // or better even EpicorPartViewModel, since you're binding to it
{
// ...
public string PartNum
{
get { return _part; }
set {
_part = value;
RaisePropertyChanged(nameof(PartNum)); } // what's used here should match the property name, it's easy to keep this consistent with `nameof()`
}
// also, you use 'Desc' here, but 'Description' in the grid...
Next, if I understand the problem correctly, you need to bind the ItemsSource of your DataGrid to the EpicorParts property of your ViewModel.
Each column can then bind to one of the properties of the EpicorPart class:
<DataGrid ItemsSource="{x:Static model:ViewModel.EpicorParts}" >
... columns come here, see below
</DataGrid>
To display the value of 'Part Number' in the combobox column, bind the SelectedItemBinding property to that property of EpicorPart. ItemsSource of DataGridComboBoxColumn is for storing the available options in that combobox (the list you see when the combobox is open). So you should bind it to that list. Not sure if that exists already, but it could look something like this:
<materialDesign:DataGridComboBoxColumn
Header="Part Number"
SelectedItemBinding="{Binding PartNum}" ---> this is for what is being displayed as 'selected' in the combobox
ItemsSource="{Binding AvailablePartNumbers}" /> ---> not sure if you have it, but the point is - it's probably a separate list
<materialDesign:DataGridTextColumn
Header="Description"
Binding="{Binding Description}" --> here this should work already, as long as the whole grid is bound to the ObservableCollection
/>

Binding DataGrid to ObservableCollection of ModelWrapper<Model>

Auto-Generating Columns for the Properties of a Wrapped-Object
I am wanting to have AutoGeneratedColumns="true" but my I'm not sure how to do this with this ModelWrapper design. (I'm following the early stages of PluralSight.com's "Advanced Model Treatment" course).
My <datagrid> in a working state (This has manually defined columns, but I want autogenerated columns - while persisting with this ModelWrapper pattern):
<DataGrid Grid.Row="0"
Grid.RowSpan="2"
Grid.Column="1"
DockPanel.Dock="Top"
ItemsSource="{Binding Segments}"
SelectionUnit="FullRow"
AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Width="1*" Binding="{Binding Model.Name}"/>
<DataGridTextColumn Header="Notes" Width="2*" Binding="{Binding Model.Notes}"/>
</DataGrid.Columns>
</DataGrid>
Note: that the DataContext for the <datagrid> above (i.e. the associated ViewModel) exposes an ObservableCollection<ModelWrapper<Segment>> called Segments.
Here is my ModelWrapper class that is used to wrap a basic class with only CLR properties (I hazard a guess the problem is not here - or in my model definition further below):
Note: Observable implements INotifyPropertyChanged.
internal class ModelWrapper<T> : Observable
{
public ModelWrapper(T model)
{
if (model == null)
throw new ArgumentNullException(nameof(model));
Model = model;
}
public T Model { get; }
protected void SetValue<TValue>(TValue value, [CallerMemberName] string propertyName = null)
{
var propertyInfo = Model.GetType().GetProperty(propertyName);
var currentValue = propertyInfo.GetValue(Model);
if (Equals(currentValue, value)) return;
propertyInfo.SetValue(Model, currentValue);
OnPropertyChanged(propertyName);
}
protected TValue GetValue<TValue>([CallerMemberName] string propertyName = null)
{
var propertyInfo = Model.GetType().GetProperty(propertyName);
return (TValue) propertyInfo.GetValue(Model);
}
}
Here is the model class that is wrapped by the ModelWrapper:
internal class Segment
{
public string Notes { get; set; }
public string Name { get; set; }
}
If I set AutoGenerateColumns to True, then I only get one column with the Header ...Model (i.e. ToString()-ing the Model object).
I've rushed ahead of the course and am trying to implement something with the ModelWrapper pattern. Can I autogenerate the columns, as per the properties of the ordinaryCLRobject?
How? What and I doing wrong? ... thanks in advance. :-)
My problem was that I was creating ObservableCollections of type ModelWrapper instead of, in my case, SegmentWrapper : ModelWrapper<Segment>.
I can then remove Model. from the binding path in my xaml columns, i.e.:
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Width="1*" Binding="{Binding Name}"/>
<DataGridTextColumn Header="Notes" Width="2*" Binding="{Binding Notes}"/>
</DataGrid.Columns>

Empty DataGrid when binding a List

I'm trying to bind a list of class A:
class ClassA
{
public int Number;
public int[,] AnArray;
}
To my DataGrid:
<DataGrid Name="ResultsDataGrid" DataContext="{Binding ResultsForGrid}" ItemsSource="{Binding Path=ResultsForGrid}" AutoGenerateColumns="False" Margin="0,10,176,0" Height="220.378" VerticalAlignment="Top" >
<DataGrid.Columns>
<DataGridTextColumn Header="Number" Binding="{Binding Path=Number}"/>
<DataGridTextColumn Header="Array Rows" Binding="{Binding Path=AnArray.GetLength(0)}"/>
<DataGridTextColumn Header="Array Columns" Binding="{Binding Path=AnArray.GetLength(1)}"/>
</DataGrid.Columns>
</DataGrid>
To bind it I'm using a List<ClassA> ResultsForGrid; which contains all the elements I'll ever want to show in the view and is declared as a global variable in my window class. When my list is filled with all the elements of ClassA I need to display I set the ItemsSource like
ResultsDataGrid.ItemsSource = ResultsForGrid;
The odd thing is that when I run the code I get a dataGrid with the correct headers and correct number of rows (number of elements in ResultsForGrid) but completely empty.
I've tried many combinations of Bindings and DataContext since this view seems to be prone to get questions on the web but all to no avail.
Your
class ClassA
{
public int Number;
public int[,] AnArray;
}
should implement INotifyPropertyChanged and fields should be replaced by properties:
private _number;
public int Number
{
get { return _number; }
set
{
_number= value;
OnPropertyChanged();
}
}
You can't use methods in XAML:
<DataGridTextColumn Header="Array Rows" Binding="{Binding Path=AnArray.GetLength(0)}"/>
Create property that wraps this AnArray.GetLength(0) and bind to that property.
You also need class that wraps List<ClassA> and the instance of that class should be DataContext of your DataGrid instance.

WPF Datagrid NotifyOfPropertyChange doesn't work

i have a view with two datagrid and one button.
The first datagrid contains a list of articles, the second is empty and the user after press a button will add in this datagrid the article selected.
I add Caliburn.Micro in this project for use the "Screen". The problem is that also if i press add article button, nothing changed and the datagrid is always empty.
This is the XAML code:
<DataGrid temsSource="{Binding Articles}"
AutoGenerateColumns="False" HorizontalAlignment="Left" CanUserAddRows="False" >
<DataGrid.Columns>
<DataGridTextColumn Header="Id" Binding="{Binding Id}"/>
<DataGridTextColumn Header="Name" Binding="{Binding Name}"/>
<DataGridTextColumn Header="Price" Binding="{Binding Price}" />
</DataGrid.Columns>
</DataGrid>
And this the ViewModel code:
public class ShellViewModel : Screen
{
public List<Article> _articles;
public List<Article> Articles
{
get { return _articles; }
set
{
if (value != _articles)
{
_articles = value;
NotifyOfPropertyChange("Articles");
}
}
}
public void AddArticle()
{
Articles.Add(new Article
{
Id = ArticleSelected.Id,
Name = ArticleSelected.Name,
Price = ArticleSelected.Price,
});
NotifyOfPropertyChange("Articles");
}
}
Where I wrong ?
the Issue is not because of property notification but the change of collection is not notified so perhaps using ObservableCollection<T> will solve your issue
public ObservableCollection<Article> _articles;
public ObservableCollection<Article> Articles
{
....
Change your List<Article> _articles to ObservableCollectiont<Article> _articles , If you use ObservableCollection, then whenever you modify the list, it will raise the CollectionChanged event - an event that will tell the WPF binding to update. Check INotifyCollectionChanged
public ObservableCollection<Article> _articles;
public ObservableCollection<Article> Articles

How to commit changes in WPF DataGrid to ObservableCollection Source

I have DataGrid Conrol
<DataGrid Name="dataGrid" ItemsSource="{Binding Faculties}">
<DataGrid.Columns>
<DataGridTextColumn Header="Название" Width="*" Binding="{Binding Title, UpdateSourceTrigger=LostFocus, Mode=TwoWay}"/>
</DataGrid.Columns>
</DataGrid>
and View Model
private ObservableCollection<Faculty> faculties = new ObservableCollection<Faculty>();
public ObservableCollection<Faculty> Faculties
{
get { return faculties; }
set
{
faculties = value;
RaisePropertyChanged("Faculties");
}
}
Faculty class:
public class Faculty
{
public string Title { get; set; }
}
How to save changes in DataGrid to my collection? Two-Way Binding does not help
Unfortunately your Faculty class should implement the INotifyPropertyChanged interface to make it work. (The ObservableCollection will only force updates if the collection itself changes - elements are added or removed - and not when properties of elements in the collection change.)

Categories