WPF datagrid bound to datatable - c#

To set the scene; I have 2 datatables, assets and models, and a datagrid which is bound to the asset datatable; the tables are populated via a database pull. When creating a new asset the user will pick the model from a dropdown list of models, the models combobox is bound to the models datatable and shows the display name, from the model table, rather than the model ID. The text field of the model dropdown updates the new assets datatable and shows as the ID, which is how the asset table is linked to the model table and is what is required at export time.
This all currently works as expected however for user friendliness i want to display the display name in the datagrid view of assets rather than the ID however i dont want to change the fact that the id exists in the asset table as that is what is required at export time.
In a combobox i can use displaymemberpath to shown a different value however i am unable to find an equivalent for the datagrid or a way to do what I am after, any help appreciated.
WPF code for datagrid (Bound to AssetCollection):
<DataGrid Name="newAssetRecords" AutoGenerateColumns="False" IsReadOnly="True">
<DataGrid.Columns>
<DataGridTextColumn Width="Auto" Header="AssetTag" Binding="{Binding AssetTag,UpdateSourceTrigger=Explicit}"/>
<DataGridTextColumn Width="Auto" Header="Model" Binding="{Binding Model,UpdateSourceTrigger=Explicit}"/>
</DataGrid.Columns>
</DataGrid>
WPF code for combobox (Bound to Model Collection):
<ComboBox Name="combobox_newModel" SelectedValue="{Binding Model, UpdateSourceTrigger=PropertyChanged}" DisplayMemberPath="[DisplayName]" SelectedValuePath="[SysID]" IsEditable="True"/>
C# code for asset class
public class AssetsCollection
{
public string SysID { get; set; }
public string SysUpdatedOn { get; set; }
public string AssetTag { get; set; }
public string Model { get; set; }
}
C# code for model class
public class ModelCollection
{
public string SysID { get; set; }
public string SysUpdatedOn { get; set; }
public string DisplayName { get; set; }
}
The asset's model is a sysid which would exist in the model collection.
Now obviously I haven't posted all the initialisation code for the classes or the data retrieval but hopefully that is enough to help.

You can Hide columns:
Data.Columns["IdColumn"].Visible = false;
And you can set the Header:
Data.Columns[0].HeaderText = "Something";
If you got some example-code we could possibly find some generic approches..

Issue resolved, I added a new string to the Asset class called ModelName and updated it when the combobox dropdown was closed via a lookup to the models table. (I also had to implement inotifyproperty on the asset collection)
Thanks for all your help.

Related

How do I fill a datagrid in my View with my datatable in my ViewModel

I am working on an MVVM WPF Application managing Users. I want to display all users from the Users Table in a datagrid in my UsersView.
Here is my dataGrid
<DataGrid Grid.Row="1" Grid.Column="0" Name="UsersDataGrid" AutoGenerateColumns="False" ItemsSource="{Binding UsersList}">
<DataGrid.Columns>
<DataGridTextColumn Header="First Name" Binding="{Binding UTI_PRENOM}"/>
<DataGridTextColumn Header="Last name" Binding="{Binding UTI_NOM}" />
<DataGridTextColumn Header="Login" Binding="{Binding UTI_LOGIN}" />
</DataGrid.Columns>
</DataGrid>
This is how I currently fill the data grid in my UsersView.cs:
public UsersView()
{
InitializeComponent();
getAllUsersInDataGrid();
}
private void getAllUsersInDataGrid()
{
using (GestUserDbContext context = contextFactory.CreateDbContext())
{
var users =
from UTILISATEUR in context.UTILISATEUR
select new { UTILISATEUR.UTI_PRENOM, UTILISATEUR.UTI_NOM, UTILISATEUR.UTI_LOGIN };
UsersListDataGrid.ItemsSource = users.ToList();
}
}
And I would like to dispose of the View from having this responsibility (respecting MVVM) by giving it to my UsersViewModel which is currently empty.
How should I do that, I've tried to create a UsersList variable in the ViewModel and bind it thanks to itemsSource but it didn't work. Btw my view is in a ContentControl Content Binded to My ViewModel and it works for the other uses so, I don't think it's the issue.
Hope I made myself clear enough,
Thanks in advance.
Edit:
I wanna add that my main problem here is that I can't use UsersListDataGrid.ItemsSource = users.ToList(); In my ViewModel since there is no UsersListDataGrid here. I'm trying to pass data through binding (UsersList) in ViewModel. I hope this is not more confusing.
There is no problem with your binding, you can always check with mock objects, in case you are not using a unit test:
Say this is your entity class:
public class User
{
public string? UTI_NOM { get; set; }
public string? UTI_PRENOM { get; set; }
public string? UTI_LOGIN { get; set; }
}
And this is the code to bind:
public MainWindow()
{
InitializeComponent();
getAllUsersInDataGrid();
}
private void getAllUsersInDataGrid()
{
List<User> usersContext = new()
{
new User { UTI_LOGIN = "Login1", UTI_NOM = "Nom1", UTI_PRENOM = "PreNom1" },
new User { UTI_LOGIN = "Login2", UTI_NOM = "Nom2", UTI_PRENOM = "PreNom2" },
new User { UTI_LOGIN = "Login3", UTI_NOM = "Nom3", UTI_PRENOM = "PreNom3" },
};
var users =
from UTILISATEUR in usersContext
select new { UTILISATEUR.UTI_PRENOM, UTILISATEUR.UTI_NOM, UTILISATEUR.UTI_LOGIN };
UsersDataGrid.ItemsSource = users.ToList();
}
The data is successfully binding:
So, probably you have a problem with your DbContext and fetching the data from the database.
I figured out how to bind properly, the need to use properties etc.. which resolved my problem, I'll close the post so nobody could waste time answering me !
Edit : How I did it is instead of directly modifying the datasource of my Usergrid in my View, I created a property : UserList in my ViewModel which I binded to my DataGrid ItemsSource="{Binding UsersList}". I also made UserList setter trigger OnPropertyChanged() so now it updates dynamically.
Really, the main problem I had was using variables instead of properties for the databinding.
Example:
this is a variable :
private List<User> UserList;
and this is a property :
private List<User> _UsersList;
public List<User> UsersList
{
get
{
return _UsersList;
}
set
{
_UsersList = value;
OnPropertyChanged();
}
}

Model Display Name in Data-Grid as Column

I would like to publish some information in a two-dimension data-grid. First column the name of the attribute, second the current value. Currently I created an array that I attached to the WPF data-grid and filled it upside down with an Attribute = "Username" and a Value = "Waldow". Now I would like to know if there is maybe another way by using a model class where I define every attribute as a string but in the end can display it in the same way, but have a better code.
Let`s say this is my model:
public class InformationModel
{
[Description("Hostname_Description")]
[Display(Name = "Hostname_Display")]
public string Hostname { get; set; }
[Display(Name = "Username_Display")]
public string Username { get; set; }
...... more values
}
Now I want a Datagrid like this:
Hostname | PC0004
Username | Waldow
but by just specifying the value:
<DataGrid x:Name="datagrid_information" ItemsSource="{Binding Information}" IsReadOnly="True" AutoGenerateColumns="False" Margin="11,10,10,0" HorizontalAlignment="Left" VerticalAlignment="Top" SelectionMode="Single" AlternationCount="1">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Display}" Header="Attribute"/>
<DataGridTextColumn Binding="{Binding Description}" Header="Value"/>
</DataGrid.Columns>
</DataGrid>
The model class will not be extended, meaning the entries will always be the same and no more rows will be added.
You should transform your model into a view model that contains a property per column that you want to display in the DataGrid (and bind to an IEnumerable<InformationViewModel>):
public class InformationModel
{
public string Attribute { get; set; }
public string Value { get; set; }
}
You can then set the properties based on the attributes using reflection.
There is no way to retrieve the value of the attributes in XAML though. You can only bind to public properties. It's then up to you as a developer to set these properties.
You might set the Attribute property of the sample class above to the string "Hostname", or to the value of the attribute that you can get using reflection. How the value is set doesn't matter as far as the XAML/UI is concerned.
As suggested I used an own class. So basically in my main class I set a listener on my informationModel and always when a value got changed it will launch the method Model_PropertyChanged. My InformationModel got extended by INotifyPropertyChanged, which calls OnPropertyChanged always when a value got changed:
public MainViewModel() {
// set listener
informationmodel.PropertyChanged += Model_PropertyChanged;
}
private void Model_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
System.Diagnostics.Debug.WriteLine("Property changed: " + e.PropertyName);
// Update object in list
var obj = Informationdgm.FirstOrDefault(x => x.Attribute == informationmodel.GetType().GetProperty(e.PropertyName).GetCustomAttribute<DisplayAttribute>()?.Name.ToString());
if (obj != null)
{
obj.Value = informationmodel.GetType().GetProperty(e.PropertyName).GetValue(informationmodel, null);
}
}

Instance of Object to bind and manipulate it?

I want to display an instance that the user can select by using a combobox. The combobox get all entries from a ObservableCollection<string> Names {get;set;} with a binding.
What is the best way to display all properties of this instance with a binding?
My idea was to create a ObservableCollection<Foo> Bar {get;set;}.
Now the user choosed on entry inside the combobox.
With the selected name I will search the instance with this name in ObservableCollection<Foo> Bar {get;set;} and copy it into a new instance Foo TMPBar {get;set;}.
And this instance should be binded to the GUI.
Is that a good solution way?
Rather than bind the combo box to a list of names, bind to a list of the actual objects and use the DisplayMemberPath property of the ComboBox to just show the name.
Then you've got direct access to the the object via the SelectedItem property.
So you'll need:
public ObservableCollection<Foo> Bar { get; set; }
public Foo Selected { get; set; }
in your view model, and:
<ComoboBox ItemsSource="{Binding Bar}"
DisplayMemberPath=Name
SelectedItem="{Binding Selected}" />
in your view.

WPF MVVM add row with two controls to Grid/DataGrid dynamically

I am studying mvvm and face this problem with dynamic addition - in simple win forms I have done this easily with one loop and just few specification to DataRow..
So, the task is - to put all elements from List of strings to Grid/DataGrid, that contains two columns - first for check box and second for the string-based control.
I think the best idea is to use DataGrid. So I created a wpf dialog with this DataGrid and buttons and a separate file for ViewModel.
Now my ViewModel class contains a List of strings.
And I stuck.. I have read about some ObservableCollection<UIElement> that must hold DataGridRow (??) with two controls in my case..
Edit: I am trying <DataGridCheckBoxColumn for check box and <DataGridTemplateColumn for control. So the question now is binding this two columns with a list of strings - pass value of the string to control and all OK.
Need I use an ObservableCollection for that?
When i am binding a datagrid in wpf using mvvm rather than looking at it as a collection of rows and columns i see it as a collection of objects - each row represents an individual object, and each column represents a property of that object. So in your case, you should make a class to represent what you are showing in your grid, and it will have a boolean and a string property in it (to use in the 2 columns you have stated).
public class MyListItem : ImplementPropertyChangedStuff
{
private string _myString;
private bool _myBool;
public MyListItem()
{ }
public string MyStringProperty
{
get { return _myString; }
set
{
_myString = value;
this.RaisePropertyChanged("MyStringProperty");
}
}
public bool MyBoolProperty
{
get { return _myBool; }
set
{
_myBool = value;
this.RaisePropertyChanged("MyBoolProperty");
}
}
}
Now in your viewmodel, rather than having separate lists for each column, you can have a single list. If you wish to add/remove/edit rows, then you should use the observable collection, as this has the propertychanged stuff inbuilt and will update the ui when any changes are made to the collection.
public class MyViewModel
{
private ObservableCollection<MyListItem> _items;
public ObservableCollection<MyListItem> Items
{
get { return _items; }
set
{
_items = value;
this.RaisePropertyChanged("Items");
}
}
public MyViewModel()
{
this.Items = new ObservableCollection<MyListItem>();
this.LoadMyItems();
}
public void LoadMyItems()
{
this.Items.Add(new MyListItem { MyBoolProperty = true, MyStringProperty = "Hello" };
}
}
And finally the DataGrid binding:
<DataGrid ItemsSource="{Binding Path=Items, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridCheckBoxColumn Header="MyBoolProperty" Binding="{Binding Path=MyBoolProperty}"
<DataGridTextColumn Header="MyStringProperty" Binding="{Binding Path=MyStringProperty"/>
</DataGrid.Columns>
</DataGrid>
You need a view model for data row. Something like this:
public class DataRowViewModel
{
public bool? IsChecked { get; set; }
public string Text { get; set; }
}
Then, instead of List<string>, view model for dialog should expose List<DataRowViewModel>, or, if you're planning to modify this list from code, ObservableCollection<DataRowViewModel>:
public class DialogViewModel
{
// other code here
public ObservableCollection<DataRowViewModel> DataRows
{
get { return dataRows ?? (dataRows = new ObservableCollection<DataRowViewModel>(yourStringList.Select(s => new DataRowViewModel { Text = s }))); }
}
private ObservableCollection<DataRowViewModel> dataRows;
}
Next, setup DataGrid to be bound to DataRows collection, and bind its columns to IsChecked and Text respectively.
Note, that for simplicity I've omitted INPC implementation in DataRowViewModel. This will work, but if you're going to change data row properties from view model's code, you should implement INPC.

How to show a list of objects with one extra property (which is not part of the class)?

I am developing a C# WPF application with Entity Framework 6, database first. Many classes are automatically created based on the structure of the SQL Server database.
I have i.e. a class Artist and a class Title. If I just want to show in WPF a list of Artists or Titles I bind the list to an observable collection of Artists or Titles and all is fine.
But sometime I want to show i.e. a list of Artists plus an extra field/property “Select This”. I.e. the user will see a list of 10 artists with all the properties from the Artist class (generated by EF) and additionally I want to show a checkbox so that the user can select each Artist row.
I guess I can do this by creating a new class based on the Artist class and another new class based on the Title class, etc. But somehow I think there must be an easier way to do this. But how? Any suggestions?
That's what we have MVVM for..
You should have a view model in between your EF Generated models and view, wherein you can do all such manipulations, it might sound lengthy right now but I can assure you its the finest way going fwd.
And just to answer your query here you can have an additional checkbox column. for that
you need to use a DataGrid on UI,
have a checkbox column in grid,
bind each checkbox checked to a command/eventhandler..
get the item there and play with it.
The classes which EF creates are all PARTIAL classes, which means you can add more attributes to them and use them in your program without affecting the DB.
You can easily extend a class like this:
namespace Solution.Project.Model
{
partial class Title
{
public bool IsChecked {get;set;}
}
}
EDIT:
Remember the namespace!
You may be asking for how to populate a dataGrid using an anonymous type that is generated from a linq query, like the result of a join query and so on, here a basic example how that works :
first lets define a dataGrid and bind its column to the properties
you are expecting in your anonymous type object
<DataGrid x:Name="Dg" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Clm1" Binding="{Binding Clm1}"/>
<DataGridTextColumn Header="Clm2" Binding="{Binding Clm2}"/>
<DataGridCheckBoxColumn Header="Clm3" Binding="{Binding Clm3,Mode=OneWay}"/>
</DataGrid.Columns>
</DataGrid>
then here how to populate that grid using an anonymous type
public partial class MainWindow : Window
{
public ObservableCollection<Item> DataGridItems { get; set; }
public MainWindow()
{
InitializeComponent();
DataGridItems=new ObservableCollection<Item>()
{
new Item()
{
Clm1 = "Item1"
}, new Item()
{
Clm1 = "Item2"
}, new Item()
{
Clm1 = "Item3"
}
};
Dg.ItemsSource = DataGridItems.Select((item) =>
new
{
Clm1 = item.Clm1,
Clm2= "Item2",
Clm3=true
}
);
}
}
public class Item
{
public String Clm1 { get; set; }
}
make sure to use the same Binding property name used in the dataGrid.

Categories

Resources