In ASP.NET, how to store controls in a Dictionary - c#

I am storing ASP.NET Controls (Tables, Buttons, TextBoxes) in a Dictionary in a webform application, so that they can be accessed directly from user controls, instead of having to do a recursive search for them from other user controls. When they are added to the dictionary, I can verify that the visible control on the .ascx control is identical to the object in the Dictionary: X == Y returns true. But later on, when I want to do something like changing a background color, or disabling a button, X == Y returns false. A change that I make to the object in the Dictionary (which is declared as static) is not reflected in the visible GUI control.
How do I correct this?

(it's difficult to give you a helpful answer without seeing any code, and without understanding what you're trying to achieve, but ...)
Whenever a Postback is made, a new instance of the page and all its controls is created. Therefore the control instances stored in your dictionary no longer match with the control instances on the current page instance. They are still the instances created when the page was requested for the first time.
But in any case, storing control instances in a static dictionary is a very bad idea (e.g. think about what happens when multiple users call your page in parallel).
Please explain what you want to achieve and why you think your dictionary is necessary. It will then probably be easier to help you.

The dictionary stores information about each control. However, the controls that the UI displays are separate. Every time you make a change to one element in the dictionary, you have to set the actual UI components to = their corresponding dictionary element. So
Dictionary["textBoxExampleKey"].Text = "blah";
won't change the actual textbox. To update the textbox to match the value in the dictionary, do`
textBoxExample.Text = Dictionary["textBoxExampleKey"].Text;
I recommend making a method that updates the entire UI to match the dictionary for the sake of simplicity. So this method would be something like
private void UpdateUI()
{
textBoxExample.Text = Dictionary["textBoxExampleKey"].Text;
labelExample.Text = Dictionary["labelExampleKey"].Text;
listBoxExample.DataSource = Dictionary["listBoxExampleKey"].DataSource;
Refresh();
}
The "Refresh()" method reloads your UI so that all changes are made visible, which could also be part of your problem.
TL;DR: Update your actual controls, refresh your UI.

Related

Where should you be instantiating UI controls from data objects when not an ItemsControl subclass?

TLDR version...
We're trying to create as basic Panel subclass with an observable Items property. The control then uses those data items to create one or more related child UI objects per data item.
Our first thought naturally was to simply subclass ItemsControl, but that doesn't seem to fit because it uses an ItemContainerGenerator which only generates one 'container' per item whereas again, we need to potentially create several (which aren't containers anyway.) Plus, all the created items have to be direct children on the panel, not held in a container which is why we can't go the route of data templates.
As such, I'm just using a standard Control and I'm trying to find the proper place/event where I should be instantiating/destroying the resulting child UI elements in response to changes in the Items collection.
Now the details...
First things first. If there was something like a ItemMultiContainerGenerator, that would be perfect, but I know of no such thing.
Ok, so simply monitor the collection for changes and put the UI generation in the CollectionChanged event! Right? That was our first guess too. The problem there is for every new 'Add' or 'Remove', we have to spin through all existing controls to 'defrag' certain indexing properties on them (think along the lines of a Grid.Row or ZIndex property) meaning if you add ten items, you run the defrag ten times, not once at the end.
Plus, that change event may come in on a different thread. If you attempted to dispatch to the main thread, your performance takes a nose-dive.
Our other attempt was to use MeasureOverride, since that was called only once in response to an InvalidateMeasure call, regardless of how many children we added or removed. The issues (there are many) with this approach is we lose context of whether something was added or removed, meaning we had to throw away all children and re-add back all new ones making this extremely inefficient. Plus, mucking around with the visual tree or setting bindings could cause the layout pass to execute multiple times since we were changing something that affects the layout, namingly the panels children.
What I'm trying to find is something that happens as part of the overall rendering process (i.e. from the time the control is told its invalid until it renders), but before the Measure/Layout passes are called. That way I can cache the adds/removes in the CollectionChanged event and simply mark the control as invalid, wait for this mystery event, then process the changes en mass, then send off the results to the layout engine and be done with it.
Using Reflector, I've tried to find out where the ItemsControl adds its children to the panel, but I didn't get too far considering the complexity of the control/ItemContainerGenerator pairing.
So where is the best place to create/add UI elements to a control based on data item changes?
I think you're going to have to listen to collection changes manually. There are a few reasons:
Changing your visual children will invalidate your layout. If you change your children in measure or arrange, you will have an infinite loop.
Rendering happens after layout. By changing children after layout you are asking for an infinite loop.
Hopefully you won't have to support any collections other than ObservableCollection, because it is obviously easier to deal with the one type of collection. But, I think you can definitely make a responsive control that will do these things. Here are some tips
Implement a custom Collection that has support for AddRange and RemoveRange (the INotifyCollectionChanged event supports multiple items being added or removed) so you don't have to do the same work 10 times for 10 new items.
In your collection change handler, use the e.AddedItems and e.RemovedItems instead of accessing the underlying collection. This will prevent you from receiving exceptions due to the collection changing while you're enumerating it.
Use BeginInvoke to prevent blocking the producer thread when dispatching back to the UI thread
To address your initialization concerns, implement ISupportInitialize, and use it to suspend your "defrag" process if you have to add or remove multiple items one at a time. WPF will automatically add the Begin/End calls for you when your control is created in XAML.
Derive from FrameworkElement if you don't want a ControlTemplate. Lower overhead.
If this still isn't working because the speed at which your underlying collection changes is too fast, and children are fairly simple, perhaps you should draw them in OnRender
One other option that just occurred to me is that you can schedule all these operations on the Dispatcher, so that if multiple changes happen, you would only need to do it once. Basically, you would store a reference to the operation like so:
private DispatcherOperation pendingDefragOperation;
protected void ScheduleDefrag()
{
if (pendingDefragOperation == null)
{
pendingDefragOperation = Dispatcher.BeginInvoke(
DispatcherPriority.Render, // You may want to play around with this
new Action(Defrag));
}
}
And you would call this on a CollectionChanged. And in your Defrag call, you'd set pendingDefragOperation to null.

C# How to implement a form which controls are dependant of some properties?

In my application, different controls are only used dependent of the values of properties from a particular object. The forms constructor accept this object as a parameter.
The form has always some basic functionality, no matter what properties are set of the particular object.
Now I have something like this:
if(myObject.SomeProperty)
{
myControl.Visible = true;
myOtherControl.Visible = false;
// and so on
}
At this time, the controls that are dependant of SomeProperty are buttons and tab items. However, I can imagine that in the future other controls are added to the form and are also dependant of SomeProperty.
As you might guess, I want to set this up the right way. But I don't know exactly how. How would you implement this?
There are multiple ways I can think of solving this, depending on your situation you could select the best suited to you.
1. Databinding is one elegant solution when managing the state (visibilit or other properties) of multiple control's depend on a different object. Additional details in this question
2. You could write different functions if the combination of the states is only limited to couple of cases to at most 4-5 cases. That ways you can still reason about the methods which set the state depending on the object you are depending on. Ex: Basic_Editing, Advaced_Editing, Custom_Editiong etc.
3. If the number of cases are limited you could create multiple forms (User controls) and load them on demand based on the state of the dependent property (or object you are talking about).
Just having a bunch of if else's makes your code harder to maintain, or comprehend, logically group the states so that 1. You could reason about it later, 2.Someone else understands the reason/logic 3.When there is a change required it can be localized to one of these modular methods (techniques) reducing the time to fix, and test.
I would do it like this in form constructor:
myControl.Visible = myObject.SomeProperty && !myObject.SomeOtherProperty;
myOtherControl.Visible = !myObject.SomeProperty;
....
Is it the less code and its rapidly changing.
OR
You can create separate functions that will generate controls dynamically at runtime for each form view based on object properties.
First i can see you are setting visibility on/off it means you have already controls on the form every time.. , so that not a good practice, instead create controls only when needed.
As for your scenario you can have an function like Initialize() which contains all the code for checking if showing a particular control should be shown or not and then create it and add it to Forms control collection. If any new control come to be added later you have one function to update.
A more precise answer can be given if you can provide more detail to you scenario

Is there a way to detect if the user is editing a property in a property grid?

I have a Windows form (.NET 3.5) that contains a propertygrid control. The propertygrid control gets refreshed periodically do display any changes that may have occurred in the class which it represents. I want the refresh to only occur if the user is not currently editing a property in the grid. Is there a way to detect if the user is currently editing a control?
Yes - it's a little hacky but you can find out which subcontrol of the property grid is active, and make an educated guess based on what it is. The following seems to work:
bool isEditing = (propertyGrid.ActiveControl.GetType().Name != "PropertyGridView");
There probably is, but might I recommend having your type implement INotifyPropertyChanged instead of refreshing the grid on a timer? This way you would never have to call Refresh yourself; the display would automatically update the value displayed for each property whenever that property changed.
Of course, if your type has tons of properties, or if you're using your grid to dynamically display objects of many different types, this suggestion may not be practical. It's just a thought.
This is a fairly complex problem. I'd suggest a two fold approach:
Keep track of the last time the changed events fire.
Keep track of whether or not the control has focus.
If the control hasn't been modified within a certain threshold and has focus, or if the control doesn't have focus, I'd consider that to be sufficient to determine that it is not currently being edited.
You could hook up the OnLostFocus event. This way, the control would only get updated once it no longer had focus.
protected virtual void OnLostFocus( EventArgs e)

Unexpected index change when adding a control to a FlowLayoutPanel

I have a place in my code where I am dynamically adding controls to a Top-Down arranged FlowLayoutPanel. I need the controls to appear in a certain order so what I am doing everytime is clearing the FlowLayoutPanel.Controls collection and then adding each child control in the order I want them to appear. My code does this:
private void arrangement1()
{
flowLayoutPanel1.Controls.Clear();
flowLayoutPanel1.Controls.Add(control1);
flowLayoutPanel1.Controls.Add(control2);
flowLayoutPanel1.Controls.Add(control3);
}
Most of the time this works great. However, there is one specific control that does not maintain it's position in the control collection when adding other controls after it. For instance in the following code segment:
private void arrangement2()
{
flowLayoutPanel1.Controls.Clear();
flowLayoutPanel1.Controls.Add(control1);
flowLayoutPanel1.Controls.Add(movingControl);
//movingControl current is at index = 1 in Controls.
flowLayoutPanel1.Controls.Add(control2);
//control2 is now at index = 1, movingControl got bumped to index = 2 in Controls.
flowLayoutPanel1.Controls.Add(control3);
//control3 is now at index =2, movingControl got bumped to index = 3 in Controls.
}
This only happens the first time movingControl is added to Controls. If I go back and call arrangement1 and then arrangement2 a second time. The controls will appear the in intended order of:
control1
movingControl
control2
control3
This seems to be a bug in the code for Controls.Add. Either that or the documentation for .Add's behaviour is incomplete as it doesn't always add to the end of the collection. Does anyone have any insight into why this occurs. The obvious "fix" is to just call:
arrangement2();
arrangement1();
arrangement2();
However, that seems like a very poor solution to some other underlying problem.
Thanks in advance for the help!
EDIT: Note that each of these controls is a member of a custom view class so they persist after the Controls collection is Cleared. However, these controls are not stored in any sort of ordered collection. They are just members of this custom class. The code shown above works correctly as shown. However, in the context of my GUI program it has the described erroneous behaviour. I would post more code if I had any idea what would be helpful but there is a lot of code that touches these peices. This is all of the code that executes for the described action.
What I'm really looking for is what possible scenarios cause a Controls.Add to Insert a control not at the last index of the collection. Specifically after a call to Clear() and with NO Remove() calls.
First running Arrangement1 gives me:
control1
control2
control3
Running arrangement2 gives me:
control1
movingcontrol
control2
control3
Your code, pure as it is posted, works for me.
However, recently i encountered a similar problem. I was holding the controls in a list, then itterating through this to add them to the flowlayout. Removing a single item from the list, then adding a new control would not insert the new control at the end, it would replace the vacant spot left by the deleted item.
So in summary, i suspect it's something you haven't posted, possibly where you store the controls themselves? do you store them in an array or a List?
Would it not be easier to just toggle the visibilty of movingControl?
But I guess that answer is based on your example such that if more rearranging is going on then this may not apply.
In conjunction with visibility toggling, you could also look into using ControlCollection.SetChildIndex() instead which seems more appropriate and seems more likely to produce a smoother re-ordering.

Control changes in a Windows Forms C# application

I have a form with several components, like TextBox and ComboBox, and I need to know when click in the out button if there was any changes in the form. Is there a way to do this?
You could create a generic change event handler which sets a flag on change, and then assign all the controls' Change events to it.
This could probably be done pretty easily by looping through all of your controls onload.
You could loop through all controls but this would have to be recursive because a control can contain controls, e.g. (no null checks for brevity):
private void IterateOverControls( Control parent )
{
ProcessControl( parent );
foreach( Control control in parent.Controls )
IterateOverControls( control );
}
In ProcessControl you could hook up event handlers to handle OnEnter (to store the state) and OnLeave (to check the current state against the stored state). You'd need to unhook all the event handlers when disposing. Also, the code to store check the state would have to change for different control types, e.g. TextBox would be the Text property, but a radio button would be an index, etc. Obviously this becomes simpler if you can compare form state to your underlying data store state, in which case you can just make the comparison on each OnLeave event.
One thing also to consider is do you need to track real changes? For example, I have 2 radio buttons: A and B. I check B (a change), so the out button or whatever has its Enabled property changes. I then click on A (i.e. back to my original state). Do you need to revert the button at that point?
This is why you should look towards a model view controller approach :)
The easiest way to do this would be to simply use a variable on the form named something like "IsChanged." Set it false when the form is initially displayed, and set it true if they make any changes.
Alternately, you could record the values of everything when the form is displayed, and when they finish, check the current values against the old ones to see if anything changed.
If this is already nearly finished, and you need something quick it's probably going to be easier to just always assume that something has changed, then in your update logic afterwards (whatever it's doing) don't update stuff that is still the same.
As someone else mentioned, it's very possible for someone to change something, then change it back. What would you want to do in that case? You won't be able to maintain a proper dirty state of the form without a fair bit of additional work.. this is something that you need to plan for before you start, really.

Categories

Resources