Object databinding c# winforms not working - c#

I've seen a bunch of posts regarding databinding to databases but none of them have helped with databinding to an existing object in memory. I've also looked at several stack overflow posts where people have said the following code should've bound the properties on my combo box:
projectData = new ProjectData();
this.parentTypeComboBox.DataSource = projectData.MobList;
this.parentTypeComboBox.DisplayMember = "MobType";
this.parentTypeComboBox.ValueMember = "MobType";
My data object has public getters/setters for it's various properties and I've added the INotifyPropertyChanged interface on the classes but do not attach any listeners to the event as of now. From what I've read this should've been all I had to do to get the control to bind to my data object. Any idea why I'm not seeing my combo box get populated with data when my object list changes?
Project data class:
public class ProjectData : INotifyPropertyChanged
{
public static string PROJECT_OUTPUT_DIRECTORY = "..\\";
private List<Mob> _mobList;
public List<Mob> MobList
{
get { return _mobList; }
set { _mobList = value; OnPropertyChanged("MobList"); }
}
public ProjectData()
{
MobList = new List<Mob>();
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Mob Class:
//Snippet mob of class
public partial class Mob : IEquatable<Mob>, INotifyPropertyChanged
{
public Mob()
{
dataAttributeField = new List<MobDataAttribute>();
}
private List<MobDataAttribute> dataAttributeField;
private string mobTypeField;
private string parentTypeField;
/// <remarks/>
[System.Xml.Serialization.XmlElementAttribute("DataAttribute")]
public List<MobDataAttribute> DataAttribute
{
get
{
return this.dataAttributeField;
}
set
{
this.dataAttributeField = value;
OnPropertyChanged("DataAttribute");
}
}
/// <remarks/>
[System.Xml.Serialization.XmlAttributeAttribute()]
public string MobType
{
get
{
return this.mobTypeField;
}
set
{
this.mobTypeField = value;
OnPropertyChanged("MobType");
}
}
}

Using this projectData = new ProjectData(); the MobList is an empty list yet.
If you didn't populate data, you should populate your data to list to show it in ComboBox.
Remember that every time you populate data, you should update DataSource property of your ComboBox:
this.comboBox1.DataSource = parent.Childs;
If you are bound to a data source that does not implement the
IBindingList interface, such as an ArrayList, the bound control's data
will not be updated when the data source is updated. For example, if
you have a combo box bound to an ArrayList and data is added to the
ArrayList, these new items will not appear in the combo box.
Here is a sample:
public partial class SampleForm : Form
{
public SampleForm()
{
InitializeComponent();
}
private void SampleForm_Load(object sender, EventArgs e)
{
//Initialize parent and populate its Childs
var parent = new Parent()
{
ParentName = "Parent 1",
Childs = new List<Child>{
new Child(){ChildName= "Child1"},
new Child(){ChildName= "Child2"}
}
};
this.comboBox1.DataSource = parent.Childs;
this.comboBox1.DisplayMember = "ChildName";
this.comboBox1.ValueMember = "ChildName";
}
}
public class Parent
{
public Parent()
{
Childs = new List<Child>();
}
public string ParentName { get; set; }
public List<Child> Childs { get; set; }
}
public class Child
{
public string ChildName { get; set; }
}
Screenshot:

Related

checking if datagrid has values

I am working on a C# project which includes WPF. I was wondering, If I could somehow check If my data grid contains certain element.
For example,
I have combo box whose itemsSource is some list of objects. Now, when an user selects an item from the combo box and presses the button
below in data grid (in same window) that item shows up.
I want to forbid the user to select same item more than once and for example put MessageBox with error message. How could I do that?
Code
This Window:
public partial class AvioWindowAddNEdit : Window
{
Avio avio;
public enum Stage { ADD, EDIT};
Stage stage;
public AvioWindowAddNEdit(Avio avio, Stage stage = Stage.ADD)
{
InitializeComponent();
this.avio= avio;
this.stage= stage;
textboxCode.DataContext = avio;
comboboxListofFlights.ItemsSource = Aplikacija.Instance.Flights;
comboboxListofFlights.DataContext = avio;
datagridListofFlights.ItemsSource = avio.ListofFlights;
datagridListofFlights.ColumnWidth = new DataGridLength(1, DataGridLengthUnitType.Auto);
if (stage== Stage.EDIT)
{
textboxCode.IsEnabled = false;
}
}
}
Button which adds selected item to data grid:
private void btnAddFlight_Click(object sender, RoutedEventArgs e)
{
avio.ListOfFlights.Add(comboboxListOfFlights.SelectedItem as Flight);
}
Singleton class for loading in all of my data:
class Aplication
{
public ObservableCollection<User> Users { get; set; }
public ObservableCollection<Airport> Airports { get; set; }
public ObservableCollection<Flight> Flights{ get; set; }
public ObservableCollection<Avio> Avios { get; set; }
public string LoggedInUser { get; set; }
private static Aplication instance = new Aplication();
public static Aplication Instance
{
get
{
return instance;
}
}
private Aplication()
{
Users= new ObservableCollection<User>();
Airports = new ObservableCollection<Airport>();
Flights = new ObservableCollection<Flight>();
Avios= new ObservableCollection<Avio>();
FillInData(); //method where I filled in all of these ObservableCollections
}
}
My class:
public class Avio : ObservableObject, ICloneable
{
//observableobject is an object where I implemented INotifyPropertyChanged
private string code;
public string Code
{
get { return code; }
set { code= value; OnPropertyChanged("Code"); }
}
private ObservableCollection<Flight> listOfFlights;
public ObservableCollection<Flight> ListOfFlights
{
get { return listOfFlights; }
set { listOfFlights= value; OnPropertyChanged("ListOfFlights"); }
}
private bool active;
public bool Active
{
get { return active; }
set { active= value; OnPropertyChanged("Active"); }
}
public Avio()
{
active= true;
ListOfFlights = new ObservableCollection<Flight>();
}
public Avio(string code)
{
active= true;
ListOfFlights = new ObservableCollection<Flight>();
Code= code;
}
}
You could use an ObservableCollection as an ItemsSource for your DataGrid. In that way you'll always have easy access to the data via code.
Check out this tutorial as a starting point (this uses ListBox instead of DataGrid, but it's easily adaptable to DataGrid).

WPF DataGrid DataBinding is not displayed

I have a DataGrid in my current WPF Application which I would like to bind to a ViewModel that holds a ObservableCollection. The user can enter search values in some TextBoxes and after enter has been hit I am performing an query to our database that retunrs a table of records. From these records I am populate the data for the ObservableCollection. I am now struggeling now that the datagrid is not displaying the data.
I have read a howl bunch of posts about the binding but I am still missing something I think.
Product.cs
public class Product : InotifyPropertyChanged, IEditableObject
{
public string Title { get; set; } = "";
//public Product()
//{
//}
private ProductViewModel _productViewModel = new ProductViewModel();
public ProductViewModel productViewModel { get { return _productViewModel; } set { _productViewModel = value; } }
public DataTable ProductsTable { get; set; }
public void GetProducts(string filter)
{
//< --doing some stuff to fill the table-->
foreach (DataRow row in ProductsTable.Rows)
{
productViewModel.Products.Add(new Product
{
Title = (string)row["TITLE"],
});
}
}
}
ProductViewModel.cs
public class ProductViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private Product _SelectedProduct;
private ObservableCollection<Product> _Products = new ObservableCollection<Product>();
public ObservableCollection<Product> Products { get { return _Products; } set { _Products = value; } }
public ProductViewModel()
{
}
public void NotifyPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
ProductWindow.xaml
<DataGrid
Name="ProductsGrid"
AutoGenerateColumns="False"
ItemsSource="{Binding Products, Mode=TwoWay, NotifyOnSourceUpdated=True}"
SelectedItem="{Binding SelectedProduct, Mode=TwoWay}"
CanUserAddRows="False" SelectionUnit="FullRow"
VerticalAlignment="Stretch"
Grid.Row="0"
Margin="10,10,10,10"
>
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Title}" Header="Title"></DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
ProductWindow.xaml.cs
public partial class ProductWindow : Page
{
public object DialogResult { get; private set; }
//public ProductViewModel ProductViewModel;
public ProductWindow()
{
InitializeComponent();
DataContext = new ProductViewModel();//stackflow
//var ProductViewModel = products.ProductViewModel;
//ProductsGrid.DataContext = new ProductViewModel();
}
public ProductViewModel ViewModel => DataContext as ProductViewModel;
private void OnKeydownHandler(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter)
{
var tb = sender as TextBox;
Product products = new Product();
string filter = "";//performing some ifelse to create filter
products.GetProducts(filter);
//ProductsGrid.DataContext = products.ProductsTable;
//ProductsGrid.DataContext = products.productViewModel;
}
else if (e.Key == Key.Escape)
{
ProductsGrid.DataContext = null;
foreach (TextBox tb in FindVisualChildren<TextBox>(this))
{
// do something with tb here
tb.Text = "";
}
}
}
}
If DataContext is a ProductViewModel, and the Products collection of that ProductViewModel is populated, you will see rows in your DataGrid. I've tested that. It appears that the viewmodel you're giving it may not have any rows.
That said, there's a problem with your design:
Product creates a ProductViewModel. ProductViewModel creates a collection of Product. Each Product, as I just said, creates a ProductViewModel. Which creates a collection of Product. They keep creating each other until you get a StackOverflowException. If you're not seeing that, you must be calling GetProducts() from somewhere else.
But there's no need for Product to own a copy of ProductViewModel. That's like adding a car to each wheel on your car.
So let's do this instead: ProductViewModel owns a collection of Product. Just that. And we'll call GetProducts() to make sure we get some items in the grid. Your binding is fine. You just weren't populating the collection.
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ProductViewModel();
}
// Now you can call ViewModel.GetProducts(filterString) from an event handler.
// It would be more "correct" to use a Command, but let's take one step at a time.
public ProductViewModel ViewModel => DataContext as ProductViewModel;
}
Viewmodels
// You didn't include any implementation of IEditableObject. I presume
// you can add that back in to this version of the class.
public class Product : INotifyPropertyChanged, IEditableObject
{
// You weren't raising PropertyChanged here, or anywhere at all.
// In every setter on a viewmodel, you need to do that.
private string _title = "";
public string Title {
get => _title;
set
{
if (_title != value)
{
_title = value;
NotifyPropertyChanged(nameof(Title));
}
}
}
public Product()
{
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class ProductViewModel : INotifyPropertyChanged
{
public ProductViewModel()
{
GetProducts("");
}
public event PropertyChangedEventHandler PropertyChanged;
private Product _SelectedProduct;
public Product SelectedProduct
{
get { return _SelectedProduct; }
set
{
if (value != _SelectedProduct)
{
_SelectedProduct = value;
NotifyPropertyChanged(nameof(SelectedProduct));
}
}
}
public DataTable ProductsTable { get; set; }
public void GetProducts(string filter)
{
//< --doing some stuff to fill the table-->
Products.Clear();
foreach (DataRow row in ProductsTable.Rows)
{
Products.Add(new Product
{
Title = (string)row["TITLE"],
});
}
}
private ObservableCollection<Product> _Products = new ObservableCollection<Product>();
// This setter MUST raise PropertyChanged. See the Title property above for example.
public ObservableCollection<Product> Products { get { return _Products; } private set { _Products = value; } }
public void NotifyPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Update
Here's the problem: You create a new Product, which creates its own ProductsViewModel. Nothing is bound to any property of that viewmodel. You fill its collection and the DataGrid doesn't know or care, because you bound its ItemsSource to a property of a different object.
So use my suggestions above, particularly the ViewModel property of the window. I just made a change in ProductsViewModel.GetProducts() that you need to copy: Now it calls Products.Clear() before populating the collection.
if (e.Key == Key.Enter)
{
var tb = sender as TextBox;
// Don't create this
//Product products = new Product();
string filter = "";//performing some ifelse to create filter
ViewModel.GetProducts(filter);
}
else if (e.Key == Key.Escape)
{
// Setting the DataContext to null breaks everything. Never do that.
//ProductsGrid.DataContext = null;
// Instead, just clear the collection. It's an ObservableCollection so it will
// notify the DataGrid that it was cleared.
ViewModel.Products.Clear();
foreach (TextBox tb in FindVisualChildren<TextBox>(this))
{
// do something with tb here
tb.Text = "";
}
}

Bind individual elements of collection in WPF application

It is WPF application and I’m trying to bind individual collection item property in TextBlock. I search on StackOverflow and many others have asked similar questions and they have their solution working. I tried to access value same way but somehow, it’s not displaying Index value in my case so posting similar question. Please help me to identify what I'm doing wrong here.
View model
public class SequeanceViewModel
{
public ObservableCollection<Sequence> SequenceList = new ObservableCollection<ViewModel.Sequence>();
public SequeanceViewModel()
{
for (int i = 1; i <= 6; i++)
{
SequenceList.Add(new ViewModel.Sequence() { Index = i, Name = "Name goes here" });
}
}
}
public class Sequence : INotifyPropertyChanged
{
private int index { get; set; }
private bool current { get; set; }
private string name;
public int Index
{
get
{
return index;
}
set
{
index = value;
OnPropertyChanged(new PropertyChangedEventArgs("Index"));
}
}
public bool Current
{
get
{
return current;
}
set
{
current = value;
OnPropertyChanged(new PropertyChangedEventArgs("Current"));
}
}
public string Name
{
get
{
return name;
}
set
{
name = value;
OnPropertyChanged(new PropertyChangedEventArgs("Name"));
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (PropertyChanged != null)
{
PropertyChanged(this, e);
}
}
}
Window code
SequeanceViewModel sequeanceViewModel;
public Validation()
{
InitializeComponent();
sequeanceViewModel = new SequeanceViewModel();
this.DataContext = sequeanceViewModel;
}
Binding in xaml
<TextBlock Text="{Binding SequenceList[0].Index, Mode=OneWay}"></TextBlock>
Since you can only bind to public properties, you must define SequenceList as a property and not as a public field:
public ObservableCollection<Sequence> SequenceList { get; } = new ObservableCollection<ViewModel.Sequence>();
You must expose SequenceList as a property instead of a public variable. Otherwise you cannot bind to it.

Update class property in xamarin Forms

I am working on mobile app using xamarin forms, I have a list of object. I have added the rows in list and raise property using this OnPropertyChanged and after save the items i want to update the status of list of object property. How we can update Status Property, Here is my code example , please check the code and update me, Thanks:-
class Test
{
public int ID{ get; set; }
public string Name { get; set; }
public bool Status { get; set; }
}
class Consume : BaseViewModel
{
void main()
{
ObservableCollection<Test> coll = new ObservableCollection<Test>();
coll = await db.GetData();
foreach (var item in coll)
{
item.Status = true;
//How we can update Status property of class
OnPropertyChanged("Status");
}
}
}
Implement INotifyPropertyChanged in your Test class:
class Test : INotifyPropertyChanged
{
public int ID { get; set; }
public string Name { get; set; }
private bool _status;
public bool Status
{
get { return _status; }
set
{
_status = value;
RaisePropertyChanged();
}
}
#region INotifyPropertyChanged implementation
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged([CallerMemberName]string propertyName = "")
{
Volatile.Read(ref PropertyChanged)?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
And if you have correct binding, after item.Status = true; UI will get change of this property.

BindingList with my class populating a ComboBox using a property of it?

I have a BindingList with my class where I would like to populate a ComboBox using a property of it so when my list changes the ComboBox would change as well.
public class UserAccess
{
public override string ToString()
{
return Access;
}
public int AccessId { get; set; }
public string Access { get; set; }
public List<string> Command = new List<string>();
public bool HasCommand(string cmd)
{
return this.Command.Any(x => x == cmd);
}
}
public BindingList<UserAccess> accessList = new BindingList<UserAccess>();
On my form load I assign it to the ComboBox:
myComboBox.DataSource = accessList;
I want to populate the box with Access or with the AccessId as value and Access as the printed name.
Problem is that it will print only the last item of the list to the combobox what am I doing wrong ?
Use DisplayMember to specify what field to use for display in the ComboBox.
Make accessList readonly to guarantee that you never recreate a new instance of the list. If you don't make it readonly, this may introduce a subtle bug, if you don't reassign DataSource whenever you recereate accessList.
private readonly BindingList<UserAccess> accessList = new BindingList<UserAccess>();
public Form1()
{
InitializeComponent();
comboBox1.ValueMember = "AccessId";
comboBox1.DisplayMember = "Access";
comboBox1.DataSource = accessList;
}
private void button1_Click(object sender, EventArgs e)
{
accessList.Add(new UserAccess { AccessId = 1, Access = "Test1" });
accessList.Add(new UserAccess { AccessId = 2, Access = "Test2" });
}
If you need to be able to change items properties in accessList (like accessList[0].Access = "Test3") and see the changes reflected in UI, you need to implement INotifyPropertyChanged.
For example:
public class UserAccess : INotifyPropertyChanged
{
public int AccessId { get; set; }
private string access;
public string Access
{
get
{
return access;
}
set
{
access = value;
RaisePropertyChanged("Access");
}
}
private void RaisePropertyChanged(string propertyName)
{
var temp = PropertyChanged;
if (temp != null)
temp(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}

Categories

Resources