I have a class that implements the INotifyPropertyChanged interface.
If I create a new instance of the object, the PropertyChanged event gets set after I retrieve a value from it.
Example:
MyItem itm = new MyItem(); //MyItem.PropertyChanged == null
string test = itm.Value; //MyItem.PropertyChanged != null
If I assign itm the value of another MyItem, the PropertyChanged event remains null.
Example:
itm = (MyItem)cboMyItemsCombobox.SelectedItem; // Properties for itm change to the values
// of the selected item, but PropertyChanged
// == null
I believe the problem lies partially in my custom constructor for the class, but I'm not entirely sure.
The goal is to have a variable to hold data for a record, called mnuitm, that is bound to
3 textbox objects. When the text in a textbox changes, the change is made to the property in mnuitm. When the property in mnuitm is changed, the change is made in the textbox.
This works if I create a new MenuItem and assign the values individually, but does not work if I assign an already populated MenuItem to mnuitm.
Here is my actual code for (hopefully) more clearity on the issue.
public partial class frmMenuItems : Form
{
private class MenuItem : INotifyPropertyChanged
{
private Int32 mid;
private string txt;
private string url;
private string scp;
public MenuItem() { }
public MenuItem(Int32 id, string txt, string url, string scp)
{
ID = id;
Text = txt;
URL = url;
Script = scp;
}
public Int32 ID
{
get
{
return mid;
}
set
{
if (mid != value)
{
mid = value;
NotifyPropertyChanged("ID");
}
}
}
public string Text {
get
{
return txt;
}
set
{
if (txt != value)
{
txt = value;
NotifyPropertyChanged("Text");
}
}
}
public string URL {
get
{
return url;
}
set
{
if (url != value)
{
url = value;
NotifyPropertyChanged("URL");
}
}
}
public string Script {
get
{
return scp;
}
set
{
if (scp != value)
{
scp = value;
NotifyPropertyChanged("Script");
}
}
}
public void Clear()
{
ID = 0;
Text = "";
URL = "";
Script = "";
}
public override string ToString()
{
return Text;
}
private void NotifyPropertyChanged(string inf)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(inf));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
private MenuItem mnuitm;
private MySqlConnection sqlcon;
public frmMenuItems()
{
InitializeComponent();
}
private void btnClose_Click(object sender, EventArgs e)
{
this.Close();
}
private void btnCancel_Click(object sender, EventArgs e)
{
mnuitm.Clear();
}
private void frmMenuItems_Load(object sender, EventArgs e)
{
string constr = "server={0};uid={1};pwd={2};database={3};";
DBItem dbi = CountyDataManager.CountyData.DBConnection;
constr = string.Format(constr, [MyHost], [MyUsername], [MyPassword], [MyDatabase]);
sqlcon = new MySqlConnection(constr);
sqlcon.Open();
mnuitm = new MenuItem();
SetBindings();
RefreshList();
}
private void SetBindings()
{
txtMenuText.DataBindings.Clear();
txtURL.DataBindings.Clear();
txtScript.DataBindings.Clear();
txtMenuText.DataBindings.Add("Text", mnuitm, "Text");
txtURL.DataBindings.Add("Text", mnuitm, "URL");
txtScript.DataBindings.Add("Text", mnuitm, "Script");
}
private void RefreshList()
{
using (MySqlCommand cmd = new MySqlCommand("SELECT `menuid`,`menutext`,`url`,`script` FROM tblindexmenu ORDER BY `menutext`", sqlcon))
{
lstMenuItems.Items.Clear();
using (MySqlDataReader rdr = cmd.ExecuteReader())
{
while (rdr.Read())
{
lstMenuItems.Items.Add(new MenuItem(Int32.Parse(rdr[0].ToString()), rdr[1].ToString(),rdr[2].ToString(),rdr[3].ToString()));
}
}
}
}
private void frmMenuItems_FormClosing(object sender, FormClosingEventArgs e)
{
sqlcon.Close();
}
private void lstMenuItems_SelectedIndexChanged(object sender, EventArgs e)
{
if (lstMenuItems.SelectedIndex > -1)
{
mnuitm = (MenuItem)lstMenuItems.SelectedItem;
}
}
}
After receiving feedback, I made the following changes:
Added CopyFrom() to MenuItem
public void CopyFrom(MenuItem itm)
{
this.ID = itm.ID;
this.URL = itm.URL;
this.Text = itm.Text;
this.Script = itm.Script;
}
I then modified the SelectedIndexChanged code to use the new function
mnuitm.CopyFrom((MenuItem)lstMenuItems.SelectedItem);
This is by design. When you write
itm = (MyItem)cboMyItemsCombobox.SelectedItem;
you haven't changed any of the properties of the MenuItem itm used to point to, rather you changed the MenuItem itm points to.
One option for what you need is add a function to MenuItem that looks like
SetFromOtherMenuItem(MenuItem other)
{
this.Url = other.Url
//etc
}
Again, PropertyChanged means that a property on some instance has changed. In your case, only one of the variables pointing to that instance changed (to point to a different instance).
Related
I have the class TestClass that has ToString overriden (it returns Name field).
I have instances of TestClass added into ListBox and at certain point I need to change Name of one of this instances, how then I can refresh it's text in ListBox?
using System;
using System.Windows.Forms;
namespace TestListBox
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
listBox1.Items.Add(new TestClass("asd"));
listBox1.Items.Add(new TestClass("dsa"));
listBox1.Items.Add(new TestClass("wqe"));
listBox1.Items.Add(new TestClass("ewq"));
}
private void button1_Click(object sender, EventArgs e)
{
((TestClass)listBox1.Items[0]).Name = "123";
listBox1.Refresh(); // doesn't help
listBox1.Update(); // same of course
}
}
public class TestClass
{
public string Name;
public TestClass(string name)
{
this.Name = name;
}
public override string ToString()
{
return this.Name;
}
}
}
try
listBox1.Items[0] = listBox1.Items[0];
I have encountered this same issue and tried all sorts of different ways to tr y to get the displayed text of an item to actually reflect the underlying item value.
After going through all the available properties I found this to be the simplest.
lbGroupList.DrawMode = DrawMode.OwnerDrawFixed;
lbGroupList.DrawMode = DrawMode.Normal;
It triggers the appropriate events within the control to update the displayed text.
Your Testclass needs to implement INotifyPropertyChanged
public class TestClass : INotifyPropertyChanged
{
string _name;
public string Name
{
get { return _name;}
set
{
_name = value;
_notifyPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void _notifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public TestClass(string name)
{
this.Name = name;
}
public override string ToString()
{
return this.Name;
}
}
However this only works if you use Columns that do not rely on the ToString() but bind the property Name
This can be done by altering your code:
somewhere in class declare
BindingList<TestClass> _dataSource = new BindingList<TestClass>();
In initializeComponent write
listBox1.DataSource = _dataSource;
Then do all operations on _dataSource instead of Listbox.
You could use a BindingList:
items = new BindingList<TestClass>( );
listBox1.DataSource = items;
listBox1.DisplayMember = "_Name";
Then to refresh the list call:
items.ResetBindings( );
edit: Also don't forget to create a get Property for Name
public string _Name
{
get { return Name; }
set { Name= value; }
}
I use the following code:
public static void RefreshItemAt (ListBox listBox, int itemIndex)
{
if (itemIndex >= 0)
{
Rectangle itemRect = listBox.GetItemRectangle(itemIndex);
listBox.Invalidate(itemRect);
listBox.Update();
}
}
I am hoping to get some pointers on what I am missing in my code.
I have a text box bound to a object property that is an item in the list, and that value doesnt update on the form if I request another item in the list.
To illustrate with example below:
txtGain value is populated after openJSONRequestFileToolStripMenuItem_Click fuction
Once I select something different in cmbSignals combobox, I expect the txtGain value to become updated since SelectedChannel is updated as well, which in turn updates the selectedindex but it doesn't happen.
Basically I want to have my txtGain value updated based on what I select in the cmbSignals. Obviously the binding is there so that I can modify the value in the text box and have it be updated in the property its bound to.
I suspect that I have to somehow force update the bindings but not sure how to do that. Any help would be appreciated.
public partial class MainForm : Form
{
private MyData req;
public MainForm()
{
InitializeComponent();
cmbSignals.DisplayMember = "Name";
cmbSignals.ValueMember = "Value";
}
private void openJSONRequestFileToolStripMenuItem_Click(object sender, EventArgs e)
{
string text = File.ReadAllText("sample.json");
req = new MyData(JsonConvert.DeserializeObject<SerializedRequest>(text));
cmbSignals.DataSource = req.SignalNames;
cmbSignals.SelectedValue = req.SelectedChannel;
SetBindings();
}
private void SetBindings()
{
txtGain.DataBindings.Add(new Binding("Text", req, "Gain"));
}
private void cmbSignals_SelectedValueChanged(object sender, EventArgs e)
{
req.SelectedChannel = Convert.ToInt32(cmbSignals.SelectedValue);
}
}
public class MyData : INotifyPropertyChanged
{
private SerializedRequest Data = new SerializedRequest();
private int selectedIndex = 0;
public int SelectedChannel
{
get
{
return selectedIndex + 1;
}
set
{
this.selectedIndex = value - 1;
}
}
public string Gain
{
get
{
return Data.signals[selectedIndex].gain;
}
set
{
Data.signals[selectedIndex].gain = value;
OnPropertyChanged("Gain");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
public List<SignalsCmbItem>SignalNames
{
get
{
List<SignalsCmbItem>channels = new List<SignalsCmbItem>();
for(int i = 0; i<Data.signals.Count;i++)
{
channels.Add(new SignalsCmbItem { Value = i + 1, Name = i+1 + " - " + Data.signals[i].label });
}
return channels;
}
}
}
Pretty annoying "feature", isn't it?.
But no worries, to get around this, add one line of code inside your cmbSignals_SelectedValueChanged(sender, e) method, after you change value of req.SelectedChannel.
txtGain.BindingContext = new BindingContext();
Problem
Problem only comes when I try to insert. However I could browse through records. When I insert a new record, the UserControl does not consider the value I typed in, instead it stores as null. I basically think on New record, it does not sync.
The Form Layout
Purpose of UserControl
The DB stores Field value in the form of JKB-932340094VN00 where I use my UserControl for split Values by - and display it in 2 TextBox in it. So one TextBox will have the Value of JKB and other will have 932340094VN00
UserControl is as follows:
public partial class ucClientAccountIDParser : UserControl, INotifyPropertyChanged
{
public ucClientAccountIDParser()
{
InitializeComponent();
id = "JKB-821230063VN00";
txtClientAccountID.TextChanged += new EventHandler(clientaccountIDChanged);
txtCustodyID.TextChanged += new EventHandler(custodyIDChanged);
}
private string _clientaccountID = string.Empty;
public string ClientaccountID
{
get { return _clientaccountID; }
set
{
_clientaccountID = value;
txtClientAccountID.Text = this._clientaccountID;
}
}
private string _custodyID = string.Empty;
public string CustodyID
{
get { return _custodyID; }
set
{
_custodyID = value;
txtCustodyID.Text = this._custodyID;
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
public string id = string.Empty;
[System.ComponentModel.Bindable(true)]
public string Text
{
get
{
return id;
}
set
{
id = value;
if (id != null)
{
string[] aVal = id.Split('-');
if (aVal.Length > 1)
{
this.CustodyID = aVal[0];
this.ClientaccountID = aVal[1];
}
else
{
this.ClientaccountID = id;
}
}
else
{
this.CustodyID = string.Empty;
this.ClientaccountID = string.Empty;
}
NotifyPropertyChanged(id);
}
}
public void NotifyPropertyChanged(string info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("Text"));
}
}
private void clientaccountIDChanged(object sender, EventArgs e)
{
this.id = string.Format("{0}-{1}", txtCustodyID.Text, (sender as TextBox).Text);
NotifyPropertyChanged(this.id);
}
private void custodyIDChanged(object sender, EventArgs e)
{
this.id = string.Format("{0}-{1}", (sender as TextBox).Text, txtClientAccountID.Text);
NotifyPropertyChanged(this.id);
}
}
This is how I bind it:
try
{
this.Init(null);
this.Text = "General Account Add/Change";
objTable = new ClientAccountBusinessTable();
this.PrimaryDataAdapters = new ClientAccountBusinessTable[1] { objTable };
this.PrimaryDataGridView = null;
objTable.KeyDBField = "CLIENTID";
PrimaryBindingSource = new BindingSource[1] { bindingSource1 };
PrimaryDataSet.Tables.Add(objTable.GetBusinessTable());
SetKeyDBControl(new Control[1] { ucClientAccountIDParser1 }, new string[1] { "CLIENTID" });
ucClientAccountIDParser1.DataBindings.Clear();
ucClientAccountIDParser1.DataBindings.Add(new Binding("Text", this.bindingSource1, "CLIENTID"));
bindingSource1.DataMember = this.PrimaryDataSet.Tables[0].TableName;
bindingSource1.DataSource = this.PrimaryDataSet;
}
catch (Exception ex)
{
objTable = null;
throw ex;
}
I cant figure out why it takes null. Let me know if I carried out design of UserControl in proper way.
I hope anybody can help me out.
In my usercontrol ViewUser the groupbox header and textblock isnt displaying UserID?
Main Window:
private void btnGeneral_Click(object sender, RoutedEventArgs e)
{
ViewUser myusercontrol = new ViewUser();
String id = (String)((Button)sender).Tag;
myusercontrol.UserID = id;
PanelMainContent.Children.Add(myusercontrol);
}
private void button1_Click(object sender, RoutedEventArgs e)
{
string uriUsers = "http://localhost:8000/Service/User";
XDocument xDoc = XDocument.Load(uriUsers);
var sortedXdoc = xDoc.Descendants("User")
.OrderByDescending(x => Convert.ToDateTime(x.Element("TimeAdded").Value));
foreach (var node in xDoc.Descendants("User"))
{
Button btnFindStudent = new Button();
btnUser.Click += this.btnGeneral_Click;
btnUser.Tag = String.Format(node.Element("UserID").Value);
//also tryed btnUser.Tag = node.Element("UserID").Value;
UserControl:
public partial class ViewUser : UserControl
{
public ViewUser()
{
InitializeComponent();
}
private string _user;
public string UserID
{
get { return _userID; }
set { _userID = value; }
}
protected override void OnInitialized(EventArgs e)
{
base.OnInitialized(e);
groupBox1.Header = UserID;
textBlock1.Text = UserID;
}
}
}
Kirsty, you should update the GroupBox and TextBlock every time the UserID property changes:
public string UserID
{
get { return _userID; }
set
{
_userID = value;
groupBox1.Header = _userID;
textBlock1.Text = _userID;
}
}
Currently you're updating the GroupBox and TextBlock only a single time in OnInitialized. But OnInitialized is called only once after the ViewUser control is initialized and never again.
This is what n8wrl meant with the second part of his answer.
You're setting groupBox1.Header and textBlock1.Text before UserID is being set. Two options:
Override OnPreRender and set them in there.
or
Set them directly from your property:
public string UserID
{
get { return textBlock1.Text; }
set
{
textBlock1.Text = value;
groupBox1.Header = value;
}
}
I have a gridview and, when a record is double-clicked, I want it to open up a new detail-view form for that particular record.
As an example, I have created a Customer class:
using System;
using System.Data;
using System.Configuration;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data.SqlClient;
using System.Collections;
namespace SyncTest
{
#region Customer Collection
public class CustomerCollection : BindingListView<Customer>
{
public CustomerCollection() : base()
{
}
public CustomerCollection(List<Customer> customers) : base(customers)
{
}
public CustomerCollection(DataTable dt)
{
foreach (DataRow oRow in dt.Rows)
{
Customer c = new Customer(oRow);
this.Add(c);
}
}
}
#endregion
public class Customer : INotifyPropertyChanged, IEditableObject, IDataErrorInfo
{
private string _CustomerID;
private string _CompanyName;
private string _ContactName;
private string _ContactTitle;
private string _OldCustomerID;
private string _OldCompanyName;
private string _OldContactName;
private string _OldContactTitle;
private bool _Editing;
private string _Error = string.Empty;
private EntityStateEnum _EntityState;
private Hashtable _PropErrors = new Hashtable();
public event PropertyChangedEventHandler PropertyChanged;
private void FirePropertyChangeNotification(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
public Customer()
{
this.EntityState = EntityStateEnum.Unchanged;
}
public Customer(DataRow dr)
{
//Populates the business object item from a data row
this.CustomerID = dr["CustomerID"].ToString();
this.CompanyName = dr["CompanyName"].ToString();
this.ContactName = dr["ContactName"].ToString();
this.ContactTitle = dr["ContactTitle"].ToString();
this.EntityState = EntityStateEnum.Unchanged;
}
public string CustomerID
{
get
{
return _CustomerID;
}
set
{
_CustomerID = value;
FirePropertyChangeNotification("CustomerID");
}
}
public string CompanyName
{
get
{
return _CompanyName;
}
set
{
_CompanyName = value;
FirePropertyChangeNotification("CompanyName");
}
}
public string ContactName
{
get
{
return _ContactName;
}
set
{
_ContactName = value;
FirePropertyChangeNotification("ContactName");
}
}
public string ContactTitle
{
get
{
return _ContactTitle;
}
set
{
_ContactTitle = value;
FirePropertyChangeNotification("ContactTitle");
}
}
public Boolean IsDirty
{
get
{
return ((this.EntityState != EntityStateEnum.Unchanged) || (this.EntityState != EntityStateEnum.Deleted));
}
}
public enum EntityStateEnum
{
Unchanged,
Added,
Deleted,
Modified
}
void IEditableObject.BeginEdit()
{
if (!_Editing)
{
_OldCustomerID = _CustomerID;
_OldCompanyName = _CompanyName;
_OldContactName = _ContactName;
_OldContactTitle = _ContactTitle;
}
this.EntityState = EntityStateEnum.Modified;
_Editing = true;
}
void IEditableObject.CancelEdit()
{
if (_Editing)
{
_CustomerID = _OldCustomerID;
_CompanyName = _OldCompanyName;
_ContactName = _OldContactName;
_ContactTitle = _OldContactTitle;
}
this.EntityState = EntityStateEnum.Unchanged;
_Editing = false;
}
void IEditableObject.EndEdit()
{
_Editing = false;
}
public EntityStateEnum EntityState
{
get
{
return _EntityState;
}
set
{
_EntityState = value;
}
}
string IDataErrorInfo.Error
{
get
{
return _Error;
}
}
string IDataErrorInfo.this[string columnName]
{
get
{
return (string)_PropErrors[columnName];
}
}
private void DataStateChanged(EntityStateEnum dataState, string propertyName)
{
//Raise the event
if (PropertyChanged != null && propertyName != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
//If the state is deleted, mark it as deleted
if (dataState == EntityStateEnum.Deleted)
{
this.EntityState = dataState;
}
if (this.EntityState == EntityStateEnum.Unchanged)
{
this.EntityState = dataState;
}
}
}
}
Here is my the code for the double-click event:
private void customersDataGridView_CellDoubleClick(object sender, DataGridViewCellEventArgs e)
{
Customer oCustomer = (Customer)customersBindingSource.CurrencyManager.List[customersBindingSource.CurrencyManager.Position];
CustomerForm oForm = new CustomerForm();
oForm.NewCustomer = oCustomer;
oForm.ShowDialog(this);
oForm.Dispose();
oForm = null;
}
Unfortunately, when this code runs, I receive an InvalidCastException error stating "Unable to cast object to type 'System.Data.DataRowView' to type 'SyncTest.Customer'".
This error occurs on the very first line of that event:
Customer oCustomer = (Customer)customersBindingSource.CurrencyManager.List[customersBindingSource.CurrencyManager.Position];
What am I doing wrong?... and what can I do to fix this? Any help is greatly appreciated.
Thanks!
EDIT:
Here is how the data is populated:
private void Form1_Load(object sender, EventArgs e)
{
// TODO: This line of code loads data into the 'northwindDataSet.Customers' table. You can move, or remove it, as needed.
this.customersTableAdapter.Fill(this.northwindDataSet.Customers);
}
It looks like your object is of type System.Data.DataRowView, not Customer. Your code returns Dataset instead of objects that you are expecting. You need to modify the code that returns your objects.
How about using BindingSource.Current instead of CurrentManager? But from the looks of it, your data source is a DataSet.
private void customersDataGridView_CellDoubleClick(object sender, DataGridViewCellEventArgs e)
{
DataGridView grid = (DataGridView) sender;
Customer customer = (Customer) grid.Rows[e.RowIndex].DataBoundItem;
}
Checks removed for brevity
Customer oCustomer = (Customer)
((DataRowView)customersBindingSource.Current).Row;
When you get the invalid cast as you have described it is due to the object is not the type you think it should be.
You can avoid the exception by using the as keyword.
Customer oCustomer = (Customer)customersBindingSource.CurrencyManager.List[customersBindingSource.CurrencyManager.Position];
becomes
Customer oCustomer = customersBindingSource.CurrencyManager.List[customersBindingSource.CurrencyManager.Position] as Customer;
If it is an invalid cast then oCustomer will be null.
Now if you are still getting the null then the object from List is not a Customer object. I would use the debugger to determine what the object is and how you would go about casting it to your Cusomter object.
You will never be able to convert directly to the Customer object because your error is
InvalidCastException error stating "Unable to cast object to type 'System.Data.DataRowView' to type 'SyncTest.Customer'". is giving you an DataRowView object. You need take the row and pull the values from the row.