WPF binding updates object it's not supposed to - c#

So my problem is as follows:
I am passing an object called Items of class MyClass into a new instance of MyWindow in WPF. And I am passing by value, not by reference. Then I bind certain controls (textboxes, comboboxes, etc.) of MyWindow to the properties of a new field called ItemsReplicate of MyClass that belongs to MyWindow and is made equal to Items. However, when I make changes to any control in MyWindow, Items's properties get overwritten too for some reason. Is this normal? Or am I missing out on something?
Here's the code description:
var passThis = (MyClass)myItem;
MyWindow wnd = new MyWindow(passThis);
public partial class MyWindow : Window
{
public MyWindow (MyClass _item)
{
InitializeComponent();
innerItem = _item;
this.DataContext = innerItem ;
}
private MyClass innerItem;
}
So in the end of this procedure, any changes made via binding affects both myItem and innerItem
Many thanks.
PS: Binding mode: Two-way

Edited after comment.
Default parameter passing for classes is per reference, even without the "ref" keyword, which only makes changes to the reference of the object itself visible "on the outside".
MyClass c = new MyClass();
MyMethod(c);
MyRefMethod(ref c);
public void MyMethod(MyClass innerc)
{
innerc = new MyClass(); //stays local, c is still its old reference
}
public void MyRefMethod(ref MyClass innerc)
{
innerc = new MyClass(); //c becomes a new reference to the local innerc
}
As in your case: You pass a copy of the reference to MyClass as '_item'. You then store that reference-copy in 'innerItem' as well as in the DataContext. It is still only one Instance of MyClass with multiple reference-copies. Every copy of a reference just points to the same instance of the class.
When you modify the values of your public properties of MyClass with the binding in WPF, the values inside of your original instance of MyClass are getting an update.
In other words: This is normal behavior.
If you really want a deep copy of your Class, the easiest way is to serialize and desialize it.

Related

Changing Controls from anywhere

What I want to do:
I want do change a background color of a button from anywhere in my code (other classes Xamarin Forms). For example a button A in Page A changes the color of button B in Page B
on Windows you can use the MethodInvoker Delegat which isn't available on Android/iOS.
Can you give me a hint?
I tried it with the text of the buttons before with the MVVM approach.
in my PageB.xaml:
<Button Name="Button_B" Text="{Binding MyText}"/>
in my PageB.cs in public PageB
BindingContext = new MVVMPageB();
in my MVVMPageB.cs
private string myText;
public string MyText
{
get => mytring;
set
{
mystring = value;
PropertyChanged?
.Invoke(this, new PropertyChangedEventArgs(MyText)));
}
if i call:
MyText("Test");
in my MVVMPageB.cs it works fine. but i dont know how to access this from anywhere else.
i tried:
var Testobjekt = new MVVMPageB() //pretty sure thats not correct
Testobjekt.MyText("Test"); //wont work
Technique 1
This is a Singleton pattern for MVVMPageB.
This works if you never have two "Page B"s. IF there is a Page B on the navigation stack (so you can "Go Back" to it), and you display ANOTHER Page B, THEN this will not work well, because both Page B's will refer to the SAME MVVMPageB instance.
public class MVVMPageB : ...
{
// "Singleton": This is the only instance of MVVMPageB.
private static MVVMPageB _It;
public static MVVMPageB It
{
if (_It == null)
_It = new MVVMPageB();
return _It;
}
// Your constructor.
// It is private; only used via "It" getter above.
private MVVMPageB()
{
...
}
}
Code in another class, to access a member of the MVVMPageB.
MVVMPageB.It.MyText("Test");
Replace this code:
BindingContext = new MVVMPageB();
With this code:
BindingContext = MVVMPageB.It;
NOTE: Because MVVMPageB.It is static, if you go to Page B a second time, it will show the values you had last time (within the same app session).
Technique 2
A more robust approach, which works even if you create another Page B, requires having some way to pass the current instance of MVVMPageB to MVVMPageA or to PageA.
A complete example depends on exactly how/where you create each page. But this shows the idea.
public class MVVMPageB : ...
{
// Your constructor. Add parameters as needed.
public MVVMPageB()
{
...
}
}
public partial class PageB : ...
{
// Convenience property - our BindingContext is type MVVMPageB.
public MVVMPageB VMb => (MVVMPageB)BindingContext;
...
}
public class MVVMPageA : ...
{
// This is here, so both MVVMPageA and PageA can find it.
public MVVMPageB VMb;
}
public partial class PageA : ...
{
// Convenience property - our BindingContext is type MVVMPageA.
public MVVMPageA VMa => (MVVMPageA)BindingContext;
...
}
Code that creates Page B and then Page A:
var pageB = new PageB();
var pageA = new PageA();
// Tell MVVMPageA about MVVMPageB.
pageA.VMa.VMb = pageB.VMb;
Methods in MVVMPageA can now access members of MVVMPageB:
VMb.MyText("Test");
Methods in PageA can now access members of MVVMPageB:
VMa.VMb.MyText("Test");
NOTE: In this dynamic technique, if you go to Page B a second time (in the same app session), it will have a new instance of MVVMPageB.
You need a singleton viewModel for this use. I usually use one for the navbar.
So every scoped page viewModel references the singleton global viewModel inside:
PageAViewModel has property NavBarModel
PageBViewModel has property NavBarModel
and so on..
So it's obvious your button will be bind as
BackgroundColor={Binding NavBarModel.ActionColor} on every different page.
Now to have a singleton and obtain its reference i can see two ways: dependency injection (DI) or single instance creation. You can read a lot about DI on the net, while for a simple case you can have a single instance model with a prop like:
private NavBarModel _current;
public NavBarModel Current
{
get
{
if (_current == null)
_current = new NavBarModel();
return _current;
}
}
then in pages viewModels constructor set NavBarModel = NavBarModel.Current;
You would need DI though to reference more models inside your singleton, or/and make your code more reusable. Good luck.

Using a DataSource with an object in Windows Forms

I have been trying to create a small form application and I wanted to try out binding a DataGridView directly to a collection of objects.
I created the following classes
public class MyClassRepository
{
public List<MyClass> MyClassList { get; set; } = new List<MyClass> { new MyClass { Name = "Test" } };
}
public class MyClass
{
public string Name { get; set; }
}
and I added the following code to a form to test. I based this off of the code in the designer after setting the BindingSource through the UI (while following this walk through https://msdn.microsoft.com/en-us/library/ms171892.aspx)
var tmp = new BindingSource();
tmp.DataMember = "MyClassList";
tmp.DataSource = typeof(MyClassRepository);
When this didn't work I started running through the code behind BindingSource to see what was happening. The setter calls ResetList which tries to create a dataSourceInstance by calling ListBindingHelper.GetListFromType. This call ultimately calls SecurityUtils.SecureCreateInstance(Type) where type is a BindingList<MyClassRepository>. This passes null to args which is passed Activator.CreateInstance which returns an empty collection.
After this ListBindingHelper.GetList(dataSourceInstance, this.dataMember) is called. This method calls ListBindingHelper.GetListItemProperties which results in a PropertyDescriptor for my MyClassList property and assigns it to dmProp.
At this point GetList calls GetFirstItemByEnumerable(dataSource as IEnumerable) where dataSource is the previously created (and empty) instance of BindingList<MyClassRepository> and returns (currentItem == null) ? null : dmProp.GetValue(currentItem);.
The value of dmProp/MyClassList is never accessed and the BindingSource is never populated with the instance I created. Am I doing something wrong? If not is there a bug in the source code? It seems to me like either SecureCreateInstance(Type type, object[] args) should be called and MyClassList should be passed via args instead of the existing call to SecureCreateInstance(Type type) or the value of dmProp should be used regardless?
If that is not correct how do I make the Designers automatically generated code set the DataSource to an instance of the object? Or do I have to inherit from BindingSource? If the latter why does it give you the option to choose a class that does not inherit from BindingSource?
As Reza Aghaei points out, in the designer, setting the BindingSource.DataSource to the “MyClassRepository” may work, however you still need to initialize (create a new) MyClassRepository object. I do not see this line of code anywhere in the posted code: MyClassRepository myRepositiory = new MyClassRepository(); The data source is empty because you have not created an instance of “MyClassRepository” and as Reza points out, this is usually done in the forms Load event.
To keep it simple, remove the DataSource for the BindingSource in the designer and simply set up the BindingSource’s data source in the form load event like below. First, create a new “instance” of the MyClassRepository then use its MyClassList property as a data source to the BindingSource. I hope this helps.
MyClassRepository repOfMyClass;
public Form1() {
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e) {
repOfMyClass = new MyClassRepository();
bindingSource1.DataSource = repOfMyClass.MyClassList;
dataGridView1.DataSource = bindingSource1;
}
Edit-----
After further review… I agree that you should be able to do as you describe. I was able to get it working as expected with the code below.
BindingSource bindingSource1;
private void Form1_Load(object sender, EventArgs e) {
bindingSource1 = new BindingSource();
bindingSource1.DataSource = typeof(MyClassRepository);
bindingSource1.DataMember = "MyClassList";
dataGridView1.DataSource = bindingSource1;
}
I followed the same steps in the “designer” and it worked as expected. Is there something else I am missing? As you stated… using MyClassRepository mcr = new MyClassRepository() appears to be unnecessary. In addition, if you cannot get it to work using one of the two ways above… then something else is going on. If it does not work as above, what happens?
Edit 2
Without creating a “new” MyClassRepository, object was unexpected and I did not realize that new items added to the list/grid were going into the bindingSource1. The main point, is that without instantiating a “new” MyClassRepository object, the constructor will never run. This means that the property List<MyClass> MyClassList will never get instantiated. Nor will the default values get set.
Therefore, the MyClassList variable will be inaccessible in this context. Example in this particular case, if rows are added to the grid, then bindingSource1.Count property would return the correct number of rows. Not only will the row count be zero (0) in MyClassList but also more importantly… is “how” would you even access the MyClassList property without first instantiating a “new” MyClassRepository object? Because of this inaccessibility, MyClassList will never be used.
Edit 3 ---
What you are trying to achieve can be done in a myriad number of ways. Using your code and my posted code will not work if you want MyClassList to contain the real time changes made in the grid by the user. Example, in your code and mine… if the user adds a row to the grid, it will add that item to the “bindingSource” but it will NOT add it to MyClassList. I can only guess this is not what you want. Otherwise, what is the purpose of MyClassList. The code below “will” use MyClassList as expected. If you drop the “designer” perspective… you can do the same thing in three (3) lines of code... IF you fix the broken MyClassRepository class and create a new one on the form load event. IMHO this is much easier than fiddling with the designer.
Changes to MyClassRepository… added a constructor, added a size property and a method as an example.
class MyClassRepository {
public List<MyClass> MyClassList { get; set; }
public int MaxSize { get; set; }
public MyClassRepository() {
MyClassList = new List<MyClass>();
MaxSize = 1000;
}
public void MyClassListSize() {
MessageBox.Show("MyClassList.Count: " + MyClassList.Count);
}
// other list manager methods....
}
Changes to MyClass… added a property as an example.
class MyClass {
public string Name { get; set; }
public string Age { get; set; }
}
Finaly, the form load event to create a new MyClassRepository, set up the binding source to point to MyClassList and lastly set the binding source as a data source to the grid. NOTE: making myClassRepository and gridBindingSource global variables is unnecessary and is set this way to check that MyClassList is updated in real time with what the user does in the grid. This is done in the button click event below.
MyClassRepository myClassRepository;
BindingSource gridBindingSource;
public Form1() {
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e) {
try {
myClassRepository = new MyClassRepository();
gridBindingSource = new BindingSource(myClassRepository.MyClassList, "");
dataGridView1.DataSource = gridBindingSource;
}
catch (Exception ex) {
MessageBox.Show("Error: " + ex.Message);
}
}
private void button1_Click_1(object sender, EventArgs e) {
MessageBox.Show("Binding source count:" + gridBindingSource.Count + Environment.NewLine +
"MyClassList count: " + myClassRepository.MyClassList.Count);
}
I hope this makes sense. ;-)
Designer sets DataSource = typeof(Something) for design-time support, for example to let you choose DataMember from a dropdown or to let you choose the data source property from dropdown while setting up data-bindings.
How do I make the Designers automatically generated code set the
DataSource to an instance of the object?
Forcing the designer to do that doesn't make much sense, because the designer doesn't have any idea about what the real data source you are going to use to load data. It can be a web service, a WCF service, a business logic layer class.
So at run-time you need to assign an instance of your list to DataSource. For example in Load event of the form.

Why don't my controls update when I change them in another method?

public partial class Person : Window
{
private static TabControl tabControl;
public Person()
{
InitializeComponent();
tabControl = new TabControl();
grid.Children.Add(tabControl);
}
public void someMethod(String name){
TabItem newTab = new TabItem();
newTab.Header = name;
tabControl.Items.Add(newTab);
}
}
public class Update{
public void createGUI(String name){
Person newPerson = new Person();
newPerson.someMethod(name);
}
}
The method someMethod is called in another class at some point after creating the class. It is called after InitializeComponent and everything in the constructor. However, the tab control still has no tabs even after someMethod finished.
Edit: To clarify, the Update method is only called once through the lifetime of the application. I only want one instance of Person.
public void createGUI(String name){
Person newPerson = new Person();
newPerson.someMethod(name);
}
There, you are creating a new Person object which is completely unrelated to the one that is being displayed to you. That new object is actually never displayed anywhere, so whatever you change about it, won’t be visible to you.
Instead, you need to change the Person object that is actually around and displayed.
Alternatively, you could also show the new object by using newPerson.Show() but that will open a new window and not change the existing one.
The simple reason is that you create a new instance of the Person class every time you update, so the change in one instance cannot be reflected in another instance.

Can't Bind the DependencyProperty of a Class Derived from DependenyObject in WPF?

Im having a control named MyControl which contains a Collection<MyClass> named MyClasses
<MyControl>
<MyClasses>
<MyClasss Value={Binding} />
</MyClasses>
</MyControl>
MyClass that is derived from DependencyObject
Public Class MyClass : DependencyObject,INotifyPropertyChanged
{
Value //propdp (DependencyProperty)
}
Im having a DataTable. I need to bind the data present in table[0][0] with the Value Property of MyClass
Binding valuebinding = new Binding();
valuebinding.Path = new PropertyPath("ItemArray[0]");
valuebinding.Source = Table.Rows[0];
valuebinding.Mode = BindingMode.TwoWay;
BindingOperations.SetBinding(myValue, MyClass.ValueProperty, valuebinding);
BindingOperations.SetBinding(textBlock, TextBlock.TextProperty, valuebinding);
Now i need to dynamically change the value of the data present in the data table ?
is there issue in it? whether the solution related to the below reasons?
The DependencyObject class overrides and seals the Equals() and GetHashCode() methods
DependencyObjects are not marked as serializable
DependencyObject has thread affinity – it can only be accessed on the thread on which it was created
NOTE: It works fine in Silverlight
You better can do one thing. Create a class with the property which you would be setting from the dataTable object, derive that class from INotifyPropertyChanged and bind the property of this class from the control. A small code snippet as shown below
class MyCodeSnippet:INOtifyPropertyChanged
{
public object MyProperty {
get;
set
{
//Register for Notification
}
}
//Implement all the INotifyPropertyChanged function
}
valuebinding.Path = new PropertyPath("MYProperty");
valuebinding.Source = MyClass;

Creating a new instance of an object on button push

It seems like it should be simple enough, but im having trouble wrapping my brain around it. Normally you would declare an object in one of a couple ways
ClassName a;
a = new ClassName();
or
ClassName a = new ClassName();
etc...
but since you're explicitly declaring these at compile time i get confused when Im supposed to code this to happen at runtime. What I want to do is have a new instance of the Class instantiated when a button is clicked. But what I'm not grasping here is how is the object name going to be named if this is happening on button click?
Even worse, Objects don't have a Name at all.
The variable you are naimg is the reference to the object.
It matters what you decide the object will belong to:
void ButtonClick_H1(...)
{
ClassName a; //local variable
a = new ClassName(); // object belongs to this method
}
private ClassName anObject; // class field
void ButtonClick_H2(...)
{
anObject = new ClassName(); // object belongs to 'this' Form
}
public partial class Form1
{
Classname myClass;
public void Button1_Click(...)
{
myClass = new Classname();
}
}
?
Well, you create the object using the code you showed above, and that will be the new instance of your Class. If you declared a inside the scope of the method it will cease to exist after the method (unless there are external references to it), but if you declare it outside the method as a class variable it will stay until the class is destroyed.
In exactly the same way as you would usually name an object.
You need to hook into the button's Click event:
this.Button.Click += new RoutedEventHandler(Button_Click);
Then use something like
private void Button_Click(object sender, RoutedEventArgs e)
{
ClassName a = new ClassName();
}

Categories

Resources