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.
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();
}
}
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.
This is my class:
class EmpDetails
{
private string _EmpName;
private int _EmpID;
private string _EmpDepartment;
private string _EmpPosition;
public string EmpName
{
get
{
return _EmpName;
}
set
{
_EmpName = value;
}
}
public int EmpID
{
get
{
return _EmpID;
}
set
{
_EmpID = value;
}
}
public string EmpDepartment
{
get
{
return _EmpDepartment;
}
set
{
_EmpDepartment = value;
}
}
public string EmpPosition
{
get
{
return _EmpPosition;
}
set
{
_EmpPosition = value;
}
}
}
}
Following is my form:
public partial class Form1 : Form
{
EmpDetails d = new EmpDetails();
public Form1()
{
InitializeComponent();
}
private void btnSet_Click(object sender, EventArgs e)
{
d.EmpName = txtName.Text;
d.EmpID = Convert.ToInt32(txtID.Text);
d.EmpDepartment = txtDepartment.Text;
d.EmpPosition = txtPosition.Text;
}
private void btnClear_Click(object sender, EventArgs e)
{
txtName.Clear();
txtID.Clear();
txtDepartment.Clear();
txtPosition.Clear();
}
private void btnGet_Click(object sender, EventArgs e)
{
txtName.Text = d.EmpName;
txtID.Text = Convert.ToString(d.EmpID);
txtDepartment.Text = d.EmpDepartment;
txtPosition.Text = d.EmpPosition;
}
}
}
I am setting the values using text boxes in form so that the values go
in to properties I have created in class.
I'm getting error like: EncapsulationAssignmentCSharp.EmpDetails
does not contain a definition for GetEmpName and no extension
method GetEmpName accepting a first argument of type
EncapsulationAssignmentCSharp.EmpDetails could be found (are you
missing a using directive or an assembly reference?
I am guessing that I have to create a constructor with parameters and
set the values using keyword this, but I'm not sure how to pass the
values to the constructor. Please help me I am not very good with
programming.
Debug and run the code and check whether your code is calling GetEmpName
i have this code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using System.ComponentModel;
using System.Collections.Specialized;
using System.Collections.ObjectModel;
namespace Bix
{
public class SettingsDataObject
{
private int id;
public int Id
{
get { return id; }
set { id = value == 0 ? Db.GetNextSettingsId() : value; }
}
private string adminEmail; public string AdminEmail {
get { return adminEmail; }
set { adminEmail = value; }
}
private int state; public int State { get { return state; } set { state = value == 0 ? 1 : value; } }
public object[] GetArray()
{
return new object[] { id, adminEmail, state };
}
public SettingsDataObject()
{
}
}
public class SettingsUIObjects : ObservableCollection<SettingsUIObject>,INotifyPropertyChanged
{
protected override void InsertItem(int index, SettingsUIObject item)
{
base.InsertItem(index, item);
// handle any EndEdit events relating to this item
item.ItemEndEdit += new SettingsUIObject.ItemEndEditEventHandler(ItemEndEditHandler);
item.PropertyChanged += new SettingsUIObject.PropertyChangedEventHandler(PropertyChanged);
}
public void ItemEndEditHandler(IEditableObject sender)
{
// simply forward any EndEdit events
if (ItemEndEdit != null)
{
ItemEndEdit(sender);
}
}
public event SettingsUIObject.ItemEndEditEventHandler ItemEndEdit;
public event SettingsUIObject.PropertyChangedEventHandler PropertyChanged;
}
public class SettingsDataProvider
{
private DataAccessLayer dl;
public SettingsDataProvider()
{
dl = new DataAccessLayer();
}
public SettingsUIObjects GetSettings()
{
try
{
SettingsUIObjects objs = new SettingsUIObjects();
List<SettingsDataObject> objDataObjects = dl.GetSettings();
foreach (SettingsDataObject obj in objDataObjects)
{
objs.Add(new SettingsUIObject(obj));
}
objs.ItemEndEdit += new SettingsUIObject.ItemEndEditEventHandler(SettingsItemEndEdit);
objs.CollectionChanged += new
NotifyCollectionChangedEventHandler(SettingsCollectionChanged);
objs.PropertyChanged += new SettingsUIObject.PropertyChangedEventHandler(SettingsPropertyChanged);
return objs;
}
catch (Exception) { return new SettingsUIObjects(); }
}
void SettingsItemEndEdit(IEditableObject sender)
{
SettingsUIObject obj = sender as SettingsUIObject;
// use the data access layer to update the wrapped data object
dl.UpdateSettings(obj.GetDataObject());
}
void SettingsPropertyChanged(INotifyPropertyChanged sender, PropertyChangedEventArgs e)
{
SettingsUIObject obj = sender as SettingsUIObject;
// use the data access layer to update the wrapped data object
dl.UpdateSettings(obj.GetDataObject());
}
void SettingsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Remove)
{
foreach (object item in e.OldItems)
{
SettingsUIObject obj = item as SettingsUIObject;
// use the data access layer to delete the wrapped data object
dl.DeleteSettings(obj.GetDataObject());
}
}
if (e.Action == NotifyCollectionChangedAction.Add)
{
foreach (object item in e.NewItems)
{
SettingsUIObject obj = item as SettingsUIObject;
// use the data access layer to delete the wrapped data object
dl.UpdateSettings(obj.GetDataObject());
}
}
}
}
public class SettingsUIObject : IEditableObject, INotifyPropertyChanged
{
private SettingsDataObject obj;
public SettingsUIObject(SettingsDataObject o)
{
obj = o;
}
public SettingsDataObject GetDataObject()
{
return obj;
}
public int Id { get { return obj.Id; } set { obj.Id = value; } }
public string AdminEmail {
get { return obj.AdminEmail; }
set { obj.AdminEmail = value; }
}
public delegate void ItemEndEditEventHandler(IEditableObject sender);
public event ItemEndEditEventHandler ItemEndEdit;
#region IEditableObject Members
public void BeginEdit() { }
public void CancelEdit() { }
public void EndEdit()
{
if (ItemEndEdit != null)
{
ItemEndEdit(this);
}
}
#endregion
public delegate void PropertyChangedEventHandler(INotifyPropertyChanged sender, PropertyChangedEventArgs e);
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
}
and i keep getting the compile error:
'Bix.SettingsUIObject' does not implement interface member 'System.ComponentModel.INotifyPropertyChanged.PropertyChanged'. 'Bix.SettingsUIObject.PropertyChanged' cannot implement 'System.ComponentModel.INotifyPropertyChanged.PropertyChanged' because it does not have the matching return type of 'System.ComponentModel.PropertyChangedEventHandler'
can anyone tell me why?
thanks
Orson
public delegate void PropertyChangedEventHandler(INotifyPropertyChanged sender, PropertyChangedEventArgs e);
public event PropertyChangedEventHandler PropertyChanged;
Your code redeclares a PropertyChangedEventHandler delegate, which hides the one declared in System.ComponentModel. So your event is of type SettingsUIObject.PropertyChangedEventHandler, not System.ComponentModel.PropertyChangedEventHandler. Since the type doesn't match the one declared in INotifyPropertyChanged, your PropertyChanged event doesn't a valid implementation of the interface.
Just remove your PropertyChangedEventHandler delegate and it should work fine.
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).