Why would overwriting .GetHashCode clear these databound values in WinForms? - c#

We have run into a strange bug that we're having problems debugging.
We have a MDI workspace that uses Microsoft CAB, DevExpress components, and .Net 3.5.
If users open two windows in the workspace that each contain a UserControl bound to two separate data models, then minimize both of them, the first window to minimize is getting it's bound fields cleared when the second one minimizes.
The .Equals and .GetHashCode methods of the data model have been overridden so that both data models are considered equal. If we change that so they are unique, we do not get this behavior.
Here's some example pseudocode showing the problem
var a = new MyWindow();
a.DataModel = new SomeClass(123);
a.ShowInMdiWorkspace();
var b = new MyWindow();
b.DataModel = new SomeClass(123);
b.ShowInMdiWorksace();
a.Minimize();
// If SomeClass.GetHashCode() is overwritten to consider two objects
// as equal based on the value passed in, then the data bindings for A
// get cleared on this call. If SomeClass.GetHashCode is unique, then
// this problem does not happen.
b.Minimize();
Here's the Call Stack when the second window gets minimized:
At the EndEditSession() call in the stack trace above, it is calling EndEditSession for the second window minimized, while by the time the Stack Trace gets past the [External Code] to the OnChange breakpoint I have set, it is firing the change method in the first window.
EndEditSession() is something custom we have implemented which looks something like this
protected void EndEditSession()
{
IBindingValue bv = null;
if (_bindingValues == null)
return;
if (_data != null)
{
foreach (KeyValuePair<string, IBindingValue> kvp in _bindingValues)
{
bv = kvp.Value;
if (bv.IsBindable)
((PropertyManager)bv.Component.BindingContext[_data]).EndCurrentEdit();
}
}
}
_bindingValues gets populated when the UserControl initializes its data bindings. The key fields are the name of the bound control, and the value fields are a custom object which stores the control itself, its name, its bound value, and default value. bv.Component returns the control that the binding is set on, which in the case of my testing is a customized DevExpress LookupEdit
_data contains the data model for the UserControl, and I can verify that it is set to the instance for the second window.
My original thought was that the BindingContext was shared so the wrong PropertyManager was being returned, however I have verified that the .BindingContext for the two forms and controls are separate.
Is it possible that having two separate copies of a UserControl bound to two separate instances of a data model would get its bindings mixed up when the GetHashCode method has been overridden so that the two objects are considered equal?
I am not very familiar with the inner workings of the WinForms binding system, or with exactly how CAB's MDI workspace gets managed.
My theory is that when the first window minimizes, it is unloading the controls to save on memory, then when the second window minimizes the internal hash table that manages the bindings is incorrectly getting confused and running an update to take data from the first minimized window (which is now blank) and updating its datasource. There are plenty of holes in this theory, however its the only thing I can think of.

I don't know the internal workings the WinForm widget, but it seems that since you've encountered an issue with overriding equals that you'd be better off working around.
If you need to evaluate equality for your own purposes:
An approach is to provide your own method to evaluate equality, rather than changing the default behavior.
If your intention is to change how the widget treats the objects:
An approach is to make a static object factory for your class. The factory could maintain a collection of all of the objects created using weak references. Weak references allow the GC to collect the objects. The factory can then check the collection of previously created objects. If a match is found then return the existing one. If not then create it. This way rather than having two different objects that evaluate two equal (override equals) you'd have a single object with two references that is equal (same memory).
Hopefully one of these other approaches will solve your problem.

BindingContext object are not sharing its fields and properties with any other BindingContext because its fields and properties are not static.
But, it is possible to have one BindingContext object for several controls.
In the first case if several controls have the same parent and have not their own BindingContext then BindingContext property of this controls will return Control.Parent(.Parent...).BindingContext object.
In the second case there are can be something like this:
var bindingContext = new BindingContext();
var a = new SomeControl();
var b = new SomeControl();
a.BindingContext = bindingContext;
b.BindingContext = bindingContext;
In the third case BindingContext can be overwritten in such a way.
I don't know what is going on in your case, so I can only recommend to do something like this before initializing data bindings:
var a = new SomeControl();
var b = new SomeControl();
a.BindingContext = new BindingContext();
b.BindingContext = new BindingContext();
If this does not solve your problem then you need to check the populating of your _bindingValues object. It is possible that during the populating of this object it is populated with wrong values.

Related

Adding multiple annotations of same type c# charting

I am attempting to add multiple flags of similar types (arrows) to a live chart using a C# windows forms project. This is to provide a label when a value falls out of a pre-defined specification.
I am currently stuck in how to create new instances of the ArrowAnnotation class so if multiple events happen there will be multiple flags for the people checking the chart. I am able to create one instance and manipulate the position to the latest data point in the series (it shouldn't be a stretch to lock it to a historical point, I just haven't done that yet.)
I have an understanding of creating multiple instances of other classes and keeping track of them with lists/ dictionaries but this one has me stumped (or maybe I don't have as good an understanding as I think?)
I can't share the code I have directly but I think I can write some example code if needed.
edit-
I am looking into using a memberwise clone to copy common attributes of each arrow and add those objects to a dictionary.
Thanks
Okay, I have managed to figure out how to do this for my use case.
When updating the live chart I can call a method if a parameter falls out of specification. In that method I create a new instance of the annotation, and the properties that I want to use as a template. (these values can also be changed with conditional logic if you want slight variation, and can be passed in with the argument) than add that newly made annotation to the annotation group. I am still looking for improvements to the code. I am still wanting a way to assign a name to the arrow, than recall that one and modify it (if I wanted to). And change the pass/fail criteria than apply annotations for the new failure points (but that is going into new territory)
// This is not the full method just the part that counts for this question.
private ArrowAnnotation floatArrow;
private void UpdateChart()
{
GenerateArrows(dateTime, sensorValue);
this.chart1.Annotations.Add(floatArrow);enter code here
}
private void GenerateArrows(DateTime x, double y)
{
floatArrow = new ArrowAnnotation();
floatArrow.Name = Convert.ToString(x);
floatArrow.ToolTip = Convert.ToString(x);
floatArrow.AxisXName = "ChartArea1\\rX";
floatArrow.AxisYName = "ChartArea1\\rY";
floatArrow.X = x.ToOADate();
floatArrow.Y = y;
floatArrow.Height = 5;
floatArrow.Width = 0;
floatArrow.BackColor = Color.Red;
}

Filtering issue with ICollectionView

ImageAlbums is an ICollectionView type and GlobalCollection.MyImageAlubms is an ObservableCollection<T> type.
ImageAlbums = CollectionViewSource.GetDefaultView(GlobalCollection.MyImageAlubms);
ImageAlbums.Filter = new Predicate<object>(this.FilterImageAlbumList);
In a view I'm using ImageAlbums for showing a filtered image list. I have filtered the list using FilterImageAlbumList method. The problem is I have used the GlobalCollection.MyImageAlubms in another place. In that view I have used the GlobalCollection.MyImageAlubms directly as source but in there the list are being showed as filtered also. I am also providing the filter method here, following code represents the filter method
private bool FilterImageAlbumList(object item)
{
AlbumModel albumMoel = (AlbumModel)item;
if(LOGIC_OF_FILTERING)
{
return false;
}
return true;
}
Is there any way to filter only ImageAlbums without affecting the GlobalCollection. FYI - I won't deep copy the Global Collection.
Your problem is caused by these two facts:
Each collection instance has only one default (instance of the) view, thus CollectionViewSource.GetDefaultView always returns the same instance for the same argument
WPF binding mechanism does not bind directly to a collection, but to its default collection view
So if you set a filter on the default view, its effects are visible wherever you bind to the collection.
If you want a separate instance of an ICollectionView your best bet is to instantiate it manually. For ObservableCollection<T> a good choice is ListCollectionView. So this should resolve your problems:
ImageAlbums = new ListCollectionView(GlobalCollection.MyImageAlubms);

Is there a way to bind properties together?

I've recently needed to synchronize values from a higher up view model (bound to a custom tab content control) to the base one (bound to the host view) for the CanExecute delegate in the host view to use. The base model has the instance of the top one, among others.
The only thing that I can think of for it to know when a value changes higher up is to subscribe to the PropertyChanged event. But, that seems excessive considering how many times that event would get fired for all of the other properties. It also doesn't feel right for MVVM (but I may be wrong).
Right now, I'm setting it all in a central method in the manager class where the magic happens to make sure the values match up:
If setDealable Then multilegViewModel.IsDealable = isDealable
multilegViewModel.IsIndicative = (Not isDealable)
' [...]
tktViewModel.IsCommandOtherEnabled = (Not isDealable)
tktViewModel.IsCommandBuyEnabled = multilegViewModel.IsBuyButtonEnabled
tktViewModel.IsCommandSellEnabled = multilegViewModel.IsSellButtonEnabled
tktViewModel.IsDealable = isDealable
tktViewModel.IsIndicative = (Not isDealable)
' [...]
But, smaller sets of the "multilegViewModel" properties are being set elsewhere, so I have to find them all and add the copy over. The risk is having another developer leave out such a pairing somewhere.
So, does anyone have ideas other than one view model subscribing to the other to ensure that the values always get set?
Note: The dual language tags are on purpose. It's a mixed language solution, such as the manager being VB.NET, but the models are C#, so I accept suggestions in either one.
UPDATE: I've changed my approach and greatly simplified it, so my initial reason for needing this is no longer valid. But, I may have an edge case or two that could still benefit from this.

Get all writeable properties of an ADLDS-Class

I'm developing an application which can deal with a MS-ADLDS-Service.
Currently it is possible to create Directory-Entries and assign values to some properties.
Not a realy exciting task until this:
Im my application it's possible (it should be) to configure which properties of a class (for instance: the CN=Person class) should be assigned with values which are evaluated at runtime in my application.
Long story short:
I want to retrieve all (writeable) properties of a class. Without creating and saving a new CN=Person-Object before.
Currently i use my schemaBinding to get the Directory-classSchema-Entry of the Person-Class (CN=Person) from where i read some property-values (like "AllowedAttributesEffective", "mayContain", "AllowedAttributes") - i get the most properties by this way - but some Properties are missing! For instance the "telephoneNumber"-Property (attributeSchema: CN=Telephone-Number)
Does anybody know how to get these properties of a class? ADSI-Edit does this: when i create a new object with adsi-edit i can assign values to all possible properties before committing the new entry.
thanks a lot for any hint!
(.net code is welcome)
I have found the solution for my task!
Some of these properties are "calculated" and not persistent at the directoryentry.
So its meant to call the RefreshCache() Method and pass the needed property names as an string array.
directoryEntry.RefreshCache(new string[] { "allowedAttributesEffective",
"allowedAttributes",
"systemMayContain",
"systemMustContain" });
After that call, the properties have values....
if (directoryEntry.Properties["systemMayContain"]).Value != null)
{
/// Success
}

How can I conditionally swap out the "User Input" part of my form?

I am creating a utility that needs to be flexible enough to use different types of data input by the user. Not just different data, as one user may enter "Rory Gallagher" another "Merle Travis" another "Louis Jordan" and yet another "Gatemouth Brown."
More like one user would enter "Rory Gallagher" another "42" and another both a date (such as 9/8/1956) and an ID value (such as "00034872184").
So the problem is with the GUI: how can I "swap out" the section of the form that prompts them for their input? It seems overkill to create several utilities that are 99% the same. But some "versions" of the app will just need a single "enter some value" label along with a TextBox, while another may need to prompt them for two or even more pieces of information.
I don't want to have an "Input Data" button on the form that would invoke another form, because when the user only needs to enter one single bit of information, that would seem quite bizarre.
I'm thinking the Strategy pattern may be in my future here, but the GUI part is still the conundrum. It would be easier if we were using WPF, but we're not. I guess I could still have a section of the form that I leave large enough to incorporate the "largest" scenario, but is that the best way to go?
UPDATE
I tried the suggestion below:
I added a panel to my form and created a user control via Add | UserControl and tried this:
panel1.Controls.Add(UserControl1);
UserControl1.Dock = DockStyle.Fill;
...but suffered an epic fail ("'PlatypiRUs.UserControl1' is a 'type' but is used like a 'variable'" and "An object reference is required for the non-static field, method, or property 'System.Windows.Forms.Control.Dock.get'")
UPDATED AGAIN
Epic success now with this:
UserControl1 usr1 = new UserControl1();
panel1.Controls.Add(usr1);
usr1.Dock = DockStyle.Fill;
I would suggest dynamically loading a different user control onto that section of the form. For design-time simplicity, you could put a panel where the dynamic control will be, and then just load it into that panel with docking turned on so it fills the whole panel. Then you can just create a separate user control for each set of data entry fields you need. For instance, if you had a panel control called pnlCustom, you could do the following in the form's load event (or wherever makes sense):
UserControl1 customControl = new UserControl1();
pnlCustom.Controls.Add(customControl);
customControl.Dock = DockStyle.Fill;
In my opinion, this is a Builder pattern.
Basically, think of your form as a "two-step view", which means the following:
Think out the structure which defines the form elements (e.g. textboxes, dropdowns, etc.),
Implement an abstract view builder (first step builder) so that in different situations you produce a different structure (remember, the structure is a common, no concrete UI components must be involved, just a structure which defines them),
Implement the abstract builder for all cases where the structure must differ,
Then implement the UI builder which takes the common structure and builds the UI coponents. Basically, this builder just translates the common structure into the platform specific UI components (such as win-forms textboxes or Html inputs).
Next step is to process the data obviously. For that you have an abstract processor and then concrete implementations for each situation (similar to the first step builder above).
Now it is clear that the UI construction and data processing can be associated 1 to 1, which means the situation is a Factory and it produces the view builder and data processor.
Pseudo code would look like this:
// construct the view.
var factory = Situation.GetCurrentSituationFactory(); //abstract factory.
var uiBuilder = factory.GetUIBuilder(); //abstract builder
var structure = uiBuilder.GetFormStructure([context goes here]); //build view definition
var viewParser = Platform.GetViewParser(); //abstract builder (step 2)
viewParser.ConstructForm([context with form goes here]); //build form UI
// later on, process the input data.
var input = viewPrser.GetInput([context with form goes here]); //input definition
var dataProcessor = factory.GetDataPocessor(); //strategy
dataProcessor.Process(input); //execute processing strategy
In addition, this does not conflict with dynamic controls or anything else you want to use to construct the form. Just implement your abstract concerns correctly.

Categories