Make column ReadOnly for ObservableCollection - c#

I am new to C# wpf and uses an ObservableCollection to populate the columns and rows of a DataGrid.
The user must be able to interact with the data in the cells with the exception of two specific columns, which should contain read-only/static content (e.g. a unique row ID).
Is there a way to make specific columns read-only in an ObservableCollection?
Or should such data be in a separate DataGrid populated by a ReadOnlyObservableCollection (which in my opinion seems overcomplicated)?
In my *.xaml file I have the DataGrid as:
<DataGrid Name="OptionsGrid" ItemsSource="{Binding Options}">
</DataGrid>
And the corresponding code is:
public ObservableCollection<OptionsData> Options { get; init; }
Options = new ObservableCollection<OptionsData>
{
new OptionsData() {Id = "123", IsActive = true, OptionCheck = true, OptionName = "Name"},
new OptionsData() {Id = "456", IsActive = false, OptionCheck = false, OptionName = "Name2"},
};
DataContext = this;
//
public class OptionsData
{
public string Id { get; set; }
public bool IsActive { get; set; }
public bool OptionCheck { get; set; }
public string OptionName { get; set; }
}
Edit: Added code example

At the moment you are automatically generating the columns in your datagrid. This is the default behaviour for a wpf datagrid.
You should set AutoGenerateColumns="False" in your datagrid tag.
Add a DataGrid.Columns tag into the datagrid.
Define your columns within that.
https://learn.microsoft.com/en-us/dotnet/api/system.windows.controls.datagrid?view=windowsdesktop-7.0
So from there
<DataGrid Name="DG1" ItemsSource="{Binding}" AutoGenerateColumns="False" >
<DataGrid.Columns>
<DataGridTextColumn Header="First Name" Binding="{Binding FirstName}"/>
<DataGridTextColumn Header="Last Name" Binding="{Binding LastName}" />
<!--The Email property contains a URI. For example "mailto:lucy0#adventure-works.com"-->
<DataGridHyperlinkColumn Header="Email" Binding="{Binding Email}" ContentBinding="{Binding Email, Converter={StaticResource EmailConverter}}" />
<DataGridCheckBoxColumn Header="Member?" Binding="{Binding IsMember}" />
<DataGridComboBoxColumn Header="Order Status" SelectedItemBinding="{Binding Status}" ItemsSource="{Binding Source={StaticResource myEnum}}" />
</DataGrid.Columns>
</DataGrid>
Note there are several types of datagrid columns.
You then have finer control of each of those columns. You can set specific ones IsReadOnly="True".
There are some other potential options but that is your simplest approach and I suggest you give that a twirl first before complicating things.

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
/>

WPF: Binding the items in a "sub-list" of a list

I have a Datagrid in WPF that I bind to my ObservableCollection List, then I create the Columns for properties that I want, like the following ones:
public int Name { get; set; }
public DateTime Date { get; set; }
public long Cod { get; set; }
public long ProductRef{ get; set; }
public List<SubProduct> ListSpecs {get; set;}
The problem is, that collection has a list of sub-products and I need to create a new column for at least the first Item of that list. The Datagrid would be like the following:
|Name|Date|Cod|ProductRef|ListSpecs[0]|ListSpecs[1]|
How can that be achieved? I don't want to add more properties to bind the items of that list.
The following should work. For example for your first item of the subcollection.
Or you could try out binding the entire subcollection to a combobox?
<DataGrid.Columns>
<DataGridTextColumn Header="item" Binding="{Binding Name}" />
<DataGridTextColumn Header="subitems" Binding="{Binding ListSpecs[0]}" /> // first item of subcollection
<DataGridTextColumn Header="subitems" Binding="{Binding ListSpecs[1]}" /> // second item of sub collection
</DataGrid.Columns>
</DataGrid>

DataGrid replaces first row instead of adding a new row,

I have a Datagrid in my WPF that is populated with a by an ObservableCollection on a doubleclick form another DataGrid (users click on preferred title and details DataGrid is populated with the associated data).
Every time I search a new title, and then make a selection, the details DataGrid replaces the first row of data that was already there. How do I make it so that the DataGrid adds a new row so that I can log multiple entities at once for exporting?
My Code Bellow:
DataGrid .cs
// This action will seach the IMDb API for the associated infromation for the IMDBID that is tagged with the title you chose in the ListBox.
private void Movie_List_MouseDoubleClick(object sender, RoutedEventArgs e)
{
// Grabs the IMDBID associated to the movie title selected to be used with the second API request.
// DataRowView row = (DataRowView)Movie_List.SelectedItems;
string titleID = ((searchInfo)Movie_List.SelectedItem).imdbID;
string newurl = "http://www.omdbapi.com/?i=" + titleID + "&r=XML";
// Prepares 2nd API URL request to get data for chosen title.
// Creates a XML Document to store the xml data that was sent back by the API.
var doc = XElement.Load(newurl);
// Creates a XML Noedlist to store the values that are going to be associated with the given attribute tag.
IEnumerable<XElement> movieList = doc.Descendants("movie");
// Creats an ObservableCollection for the Object Retrievalinfo to be displayed in the Movie_Datagrid, that can be edited.
ObservableCollection<Retrievalinfo> gridInfo = new ObservableCollection<Retrievalinfo>(movieList.Select(movieElement =>
new Retrievalinfo()
{
title = movieElement.Attribute("title").Value,
actors = movieElement.Attribute("actors").Value.Split(',').ToList(),
genre = movieElement.Attribute("genre").Value,
rated = movieElement.Attribute("rated").Value,
imdbRating = movieElement.Attribute("imdbRating").Value,
released = movieElement.Attribute("released").Value,
runtime = movieElement.Attribute("runtime").Value,
}));
Movie_DataGrid.ItemsSource = gridInfo;
}
Retrievalinfo Class (Object used for the ObservableCollection)
public class Retrievalinfo
{
public Retrievalinfo()
{
actors = new List<string>();
}
//Creating a list of info objects that will store all returned data for selected title.
public string title { get; set; }
public List<string> actors { get; set; }
public string genre { get; set; }
public string rated { get; set; }
public string imdbRating { get; set; }
public string released { get; set; }
public string runtime { get; set; }
}
}
DataGird XAML (Movie_DataGrid, holds the details of the movie selected)
<DataGrid x:Name="Movie_DataGrid"
Grid.ColumnSpan="2"
HorizontalAlignment="Left"
Margin="292,107,0,0"
VerticalAlignment="Top"
Height="214"
Width="673"
AutoGenerateColumns="False"
ItemsSource="{Binding gridInfo}">
<DataGrid.Columns>
<DataGridTextColumn Header="Title" Binding="{Binding Path=title}"/>
<DataGridTextColumn Header="Main Actor 1" Binding="{Binding Path=actors[0]}"/>
<DataGridTextColumn Header="Main Actor 2" Binding="{Binding Path=actors[1]}"/>
<DataGridTextColumn Header="Main Actor 3" Binding="{Binding Path=actors[2]}"/>
<DataGridComboBoxColumn Header="Digital Format" ItemsSource="{Binding digital}"/>
<DataGridComboBoxColumn Header="Physical Format" ItemsSource="{Binding physical}"/>
<DataGridTextColumn Header="Genre" Binding="{Binding Path=genre}"/>
<DataGridTextColumn Header="Rated" Binding="{Binding Path=rated}"/>
<DataGridTextColumn Header="IMDB Rating" Binding="{Binding Path=imdbRating}"/>
<DataGridTextColumn Header="Released" Binding="{Binding Path=released}"/>
<DataGridTextColumn Header="Runtime" Binding="{Binding Path=runtime}"/>
<DataGridComboBoxColumn Header="File Type" ItemsSource="{Binding file_type}"/>
</DataGrid.Columns>
</DataGrid>
Before binding the Datagrid, You should get the existing items to a List and add the new Item to the List and then Bind to the Datagrid.
List<Retrievalinfo> gridInfo = (List<Retrievalinfo>)Movie_DataGrid.ItemsSource;
Add the new Item to this List,
gridInfo.Add(new Retrievalinfo());
then bind the List to the datagrid,
Movie_DataGrid.ItemsSource = gridInfo;

Getting whole row value if checkbox is checked in datagrid C#

I'm trying to write a code for getting 3 values from textboxes if some of checkboxes in same row is checked.
Anyone know an easy(or hard) way to do this?
My datagrid looks like this:
I have Load button that finds a file of specific type(XML.config) somewhere in file system, after that I'm calling a method that gets some strings from that file, find substrings of them and put them in 3 separated lists. Those values are in datagrid as Type, MapTo and Name.
I accomplish this by putting all 3 lists in one ObservableCollection and after that I'm sending that ObservalableCollection to datagrid like this:
ObservableCollection<Tuple<string, string, string>> _obsCollection = new ObservableCollection<Tuple<string, string, string>>();
public ObservableCollection<Tuple<string, string, string>> MyObsCollection
{
get { return _obsCollection; }
}
tabela.ItemsSource = _obsCollection;
This is XAML code that shows binding:
<DataGrid Grid.Column="0" AutoGenerateColumns="False" Height="206" HorizontalAlignment="Left" Margin="12,265,0,0" Name="tabela" VerticalAlignment="Top" Width="556" SelectionChanged="tabela_SelectionChanged" Grid.RowSpan="2" ItemsSource="Binding MyObsCollection">
<DataGrid.Columns>
<DataGridTextColumn Header="Type" Width="122" Binding="{Binding Item1}"/>
<DataGridTextColumn Header="MapTo" Width="122" Binding="{Binding Item2}"/>
<DataGridTextColumn Header="Name" Width="121" Binding="{Binding Item3}"/>
<DataGridTemplateColumn Header="Controller">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding DataGridChecked}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Service">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding DataGridChecked}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Injection">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding DataGridChecked}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
What I'm practically trying to is to accomplish looping thou all 3 columns containing checkboxes to see which of them are selected and if any of 3 in same row is selected then I need to send all 3 string values from that row to some variable.
Anyone can help me with this. For instance I don't know how to get isSelected property from checkbox in data grid.
I was doing a lot of researching and all that I was able to find was examples for DataGridView, and almost nothing for DataGrid.
Instead of using Tuple create your own class, say RowData with all the properties you want to show as columns:
public class RowData: INotifyPropertyChanged
{
//implement INotifyPropertyChanged
public string Type { get; set; }
public string MapTo { get; set; }
public string Name { get; set; }
public bool Controller { get; set; }
public bool Service { get; set; }
public bool Injection { get; set; }
}
change ObservableCollection to use your type
public ObservableCollection<RowData> MyObsCollection { get { .... } }
and set AutoGenerateColumns="True" on DataGrid
<DataGrid
Grid.Column="0"
Grid.RowSpan="2"
AutoGenerateColumns="True"
Height="206"
Width="556"
HorizontalAlignment="Left"
Margin="12,265,0,0"
Name="tabela"
VerticalAlignment="Top"
SelectionChanged="tabela_SelectionChanged"
ItemsSource="{Binding MyObsCollection}"/>
and then to get items where any of 3 CheckBoxes is selected you do:
var selectedList = MyObsCollection.Where(n => n.Controller || n.Service || n.Injection).ToList();
To get the Checked item row from DataGrid and add it into list
List<string>chzone=new List<string>();//Declare it as globally,
//Note: if DataGrid is Binded as ,Datagrid.Itemsource=dt.defaultview;
//If datagrid contain the Checkbox, get the checked item row and add it into list
private void DataGridcheckbox_Checked(object sender, RoutedEventArgs e)
{
var checker = sender as CheckBox;
if (checker.IsChecked == true)
{
var item = checker.DataContext as DataRowView;
object[] obj = item.Row.ItemArray;//getting entire row
chzone.Add(obj[0].ToString());//getting row of first column value
}
else //This is for Unchecked Scenario
{
var item = checker.DataContext as DataRowView;
object[] obj = item.Row.ItemArray;
bool res = chzone.Contains(obj[0].ToString());
if (res == true)
{
chzone.Remove(obj[0].ToString());//this is used to remove item in list
}
}
}

Datagrid binding in WPF

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#.

Categories