While there are many questions about shallow copy vs. deep copy, I couldn't find one specific to TextBoxes and Dictionaries in VB .NET. I believe my problem is unique enough to deserve it's own thread.
In VB .NET I am trying to implement a dictionary that links an internal class called PageDisplayFields.DisplayField that is essentially an Enum to TextBoxes. The key is the Enum and the value is the System.Web.UI.WebControls.TextBox. My issue is that I'd like to then be able to change the .Text of the TextBox in the dictionary and have it also change the .Text property of the TextBox originally input into the Dictionary. When the TextBox is added to the dictionary, it appears to be a deep copy, and not a shallow copy. Changes to the .Text property of either the original TextBox or the copy in the Dictionary do not pass through to one another.
Here's a code sample of what I'm trying to do:
Private Shared _TxtBoxLookup As New Dictionary(Of PageDisplayFields.DisplayFields, System.Web.UI.WebControls.TextBox)
Private Sub SetTxtBoxLookup()
_TxtBoxLookup.Add(PageDisplayFields.DisplayFields.A, txtA)
_TxtBoxLookup.Add(PageDisplayFields.DisplayFields.B, txtB)
_TxtBoxLookup.Add(PageDisplayFields.DisplayFields.C, txtC)
_TxtBoxLookup.Add(PageDisplayFields.DisplayFields.D, txtD)
_TxtBoxLookup.Add(PageDisplayFields.DisplayFields.E, txtE)
End Sub
SetTxtBoxLookup() is called at the correct point only once, and doesn't have any errors. The structure of the _TextBoxLookup is never modified after this call.
If _TxtBoxLookup.ContainsKey(PageDisplayFields.DisplayFields.A) Then
_TxtBoxLookup.Item(PageDisplayFields.DisplayFields.A).Text = "ThisText"
End If
If this code is called, _TxtBoxLookup.Item(PageDisplayFields.DisplayFields.A).Text is actually set to "ThisText". However, txtA.Text = "" (the default value).
Similarly, if I do the reverse operation, the same effect is observed:
ModHashTable("A") = _TxtBoxLookup.Item(PageDIsplayFields.DisplayFields.A).Text
'Assume "A" is the correct key to ModHashTable
If "3" is entered into the TextBox, txtA, ModHashTable("A") will be set to "" as this is still the value that _TxtBoxLookup.Item(PageDisplayFields.DisplayFields.A).Text has stored.
If I implement storage and retrieval by directly referencing the TextBoxes, everything works as expected, so I'm 99% sure that the problem in my code is that the TextBoxes aren't shallow copying, but are deep copying. Using dictionaries in this way let's me drastically reduce the length of many methods.
Is there a way to make the Dictionary store only references to the TextBoxes? An easy way to do a shallow copy?
This is ingenious, but it probably isn't going to work as written, because of how ASP.NET web pages work.
Every time a page loads, all the controls on it are recreated and repopulated from ViewState. So, after postback, you may still have a shared Dictionary of controls ... but they are not the control objects that are currently on the page. They are the old ones.
Now, you may still be able to make this work, as long as you re-load your dictionary with the controls at the beginning of the page lifecycle, such as in Page_Load.
I would avoid using a shared Dictionary in any case, because it's going to be shared across all instances of the page. So two users accessing the same page will share the same dictionary, with potentially disastrous results.
Related
I use GridView & Repeater (and other like DropDownList) controls extensively in my application along with ObjectDataSource components and classes that serve data for ObjectDataSource (TypeName / SelectMethod attributes on ObjectDataSource). I recently noticed that SelectMethod is only called when
IsPostBack == false
Also, when I bind manually, I always bind when !IsPostBack. I never was curious how controls maintain their data between postbacks, until now (I have to create GridView with sorting/pagination etc and I want to do it efficently).
Could you explain / provide some links with descriptions how it's done?
Also I don't get one thing: when working with GridView and I iterate over rows, sometimes I need to access its GridViewRow.DataItem property in order to get backing object (usually to get some kind of ID). And sometimes it's null - sometimes not. I couldn't figure out why. However then it's null, I still can access (for instance) GridViewRow.DataKeys.
Thanks
EDIT: know when answers say that it thanks to ViewState, I have another question: If I data bind 100 business objects which are pretty heavy and I only use a few properties while data binding (let's say I also use OnRowDataBound event to render some additional data), does it mean that whole objects are serialized?
ASP.Net uses ViewState a hidden variable in the all HTML pages to maintain state of a page.
Reasd this for more understanding
http://www.google.co.in/url?sa=t&rct=j&q=&esrc=s&source=web&cd=2&cad=rja&sqi=2&ved=0CFMQFjAB&url=http%3A%2F%2Fmsdn.microsoft.com%2Fen-us%2Flibrary%2Fms972976.aspx&ei=UpE0UK3uBsOqrAe94ICwBg&usg=AFQjCNG5ErbrFH0ZYV_WW-jonIl25xEsDQ
They store it in a hidden field on the page called ViewState (or more precisely "__VIEWSTATE"). If you do a View Source on your ASPX page, you'll see it.
You too can store and retrieve state information for your page in the ViewState by accessing the ViewState property of your Page.
I'd start here http://msdn.microsoft.com/en-us/library/ms972976.aspx
So, yes, if you store a large number of large objects, there is a large amount of data being transferred to and fro to the client in the page cycle. Used carefully, Viewstate can be useful, but used carelessly, it can have negative effects.
Maybe someone can help me out. I have created a simple web user control, a date and time picker, to be dropped onto my webform. This all works very nicely, I can set properties, usee the control satisfactorily for ui etc.
When it comes time to "use" the controls selected_date_time property, I just cant get it to persist. Nothing.. I have researched and tried endlessly using viewsate, context and session. Onbiouosly session works, but its dirty, and I am using two copies of this control (start and end time), so session vars really needs to be hacked to maket his work.
Am I missing something? The control gets initialized every time something happens, and obviuosly loses its state information. The ui keeps its state tho, as I can select a date, write that date to a label, and that persists. But when I try to access my properties of the control to retreive the selected combined date and time, (that is already persisting visually), its nothing. I debuggeed and it gets initialized everytime I do any form of post on the page.
Can someone please shed some light on this for me? Its really starting to be an issue now.
Thanks in advance.
Example: (simple components)
UC _ save_method
ViewState("var_time") = "My veiwstate text"
form _read_method
dim str as string = ViewState("var_time")
form sees nothing in the viewstate var.
I have also tried it with normal properties and values, which wasn't working, which is why I moved on to viewsate var's for my properties. Right now, I am just trying to get the viewstate to work even without properties.
Seems my form and the control must have two seperate viewstates? I am a bit of a n00b concerning viewstates.
Thanks
[solution]
You have to explicitly reset your properties on the prerender method of the control. My misunderstanding was that the page and the control share the same single viewstate. Turns out the control and the page that its on, has its own independant viewstates.
So absically, on your property set function in the control, set your value in the viewstate, and upon prerender, take that viewstate value, and set the property get variable =- the value in viewstate.
You can now access the property from your page as if everything was normal and the world is not ending... pheww
Thanks to cnay for directing me.
'Usr control xyz
Protected Sub Page_PreRender(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.PreRender
my_time = ViewState("var_time")
'my_time is the get variable for property date_time
End Sub
'page use
xyz.date_time
Perhaps, you should post the complete control code to get the better answer but speaking in general terms - view-state (or control state) is a typically correct approach to persists control state such as properties. Each control instance get the different view-state bag and hence collisions are easily avoided.
Now, coming to your problem, a typical editable control first restores its state from view-state and then uses the request data to modify the state as needed. For example, a simple text-box control would persists its text value in view-state. On post-back, the text value will be restored from the view-state and then gets overwritten by the value present in the request (looked up by UniqueID property).
Now, for user controls, typically you can use child controls values (or properties) to derive the control value/properties - so these may not use view-state. However, if you add properties/state that is not backup by child controls then you may have to back them up in the view-state. So let's say your user control has two child controls - one for Date and other for time then can combine their values to get the selected date-time value for your control.
I have a composite control that has a couple of private fields that reference values in the cache and these private fields are called during the constructor method. Since a string key is used to identify the value in the cache, I must have a way of storing that string key in such a way that it is available at the time the control is instantiated, and I have to be able to reference it on postbacks without it changing.
In addition, this key is generated the first time the control is loaded, but it should not be changed again after that first time.
How can I accomplish this?
I have already tried saving it to viewstate, but that doesn't work because viewstate is not yet available at the time the control is instantiated.
I have tried using a private field and then checking against Page.IsPostback in the constructor and if it isn't postback, I assign a value to the private field, but on subsequent postbacks it looses it's value, and I can't reassign it in the Page.IsPostBack again because it is an autogenerated GUID.
This has got to be something folks have had to do before....
There isn't a lot of state info available during control construction at all, so this could be difficult. Is there some reason you can't move your code which accesses the Cache'ed info into the control's Init event?
I assume you can't use Session because the information stored is related to that specific request/postback. If it's not specific to that request, using Session could be a possibility - but I think you may encounter other problems trying to deal with control state so early in the lifetime.
After seeing your comment to the other answer; you should be able to move your code that checks for the cached datasource into the control's Init or even Load event, so the state will be available.
Also, incidentally; are you sure you really need to cache this data? That could end up taking up a lot of server memory.
Have you tried Session?
You can store anything you like in the session object for one particular user, maintaining the value / object between postbacks.
If you want to store on a global basis and not per ser basis, try Application
Although this isn't the best solution (rearranging your logic to fit the lifecycle model generally is), have you tried accessing the Request directly? I once really wanted to get the selected value off a DropDownList very early in the lifecycle so I could adjust some elements in the building, and I did it like this:
myDropDownList.SelectedValue = Page.Request.Form[myDropDownList.UniqueID];
So instead of waiting for the viewstate to load the server-side proxie's values, I just got it myself from the client-side control value that was passed in on the post. I probably would do things differently if I redesigned that page, but it seems to have worked out alright for now and it solved the problem I was having.
Ok so the obvious answer is, because the flow of a composite control demands my childcontrols to be created at a certain point in time. I got a problem i think other people must have had as well.
My control is a composite "container/collection" control. It will be fed with an object and based on that objects data it will create a number of childcontrols. So my control will render a header (always) and x-number of, say TextBox controls (based on the object it was fed with).
I'm creating my header in CreateChildControls() obviously, but i can't possibly create my TextBoxes there as well, because i don't know if the object (to base the TextBoxes on) has been fed yet? I thought of exposing a property/method to set/fed the object through, but i'm not sure when it will be called.
So what do i do? I mean i can't possibly create the TextBoxes in CreateChildControls() or can I? I mean - when is CreateChildControls() called - i know i can call EnsureChildControls() (which i already do in a property to set the innerText of the header - since i need the header to be created before setting its innerText obviously).
How about this
var c = new MyContainerControl();
c.Header = "fun";
c.TextBoxObject = myTextBoxes;
That would raise an error (or at best not create any TextBox'es) if i put the building of the TextBoxes in CreateChildControls().
Would it be more sane to instead just store the Header in a member variable and thus not having to call EnsureChildControls() in the exposed method/property setting the Header innerText. I just don't like this aproach much, since it would be complicating things by adding extra logic to store temporarely and later having to figure out when to set it (probably in PreRender).
Also i guess i could make some kind of Databound control, ensuring the data be present at the time of calling .DataBind(). I really don't like this either since last i looked at creating databound controls it got very complicated.
This really should be an easy task to solve - I know I'm missing something somewhere...
what you're describing IS a databound control. And yes, it's somewhat complicated, but it's the proper design paradigm for this type of instance.
That said, had you considered utilizing the repeater control rather than trying to roll out your own composite which behaves in the exact same manner? Rather than passing it a random object, pass it a collection or an iList with the number of text areas you're wanting.
I have a problem with Gridview sorting that is similar to others but I'm binding to a collection object as opposed to a data table.
The existing business rules and data access layers of an application follow the pattern of having an object and, if you need a collection of objects of that type, to have another class inheriting CollectionBase and implementing IBindingList.
For desktop applications, it was easy to databind a gridview to one of these objects and there weren't any problems with turning on column sorting. Everything was 'in state' in the desktop app's presentation layer.
Now that code is being moved to a new web application (ASP.NET 2.0, VB codebehind pages).
I've played around with what I had to do to only have certain columns of the collection show up in the gridview and the gridview looked pretty good. When I turned on 'allow sorting', that's when the problems showed up.
I'm getting the error about not having a .Sorting method, etc. In researching this, I found all sorts of solutions that were easily implemented with dataviews if my source was a data table. But it's not - it's a collection. I tried to "cheap shot" a datasource by converting the collection to an XML memory stream and them trying to .ReadXML back into a dataset but that didn't work [Root element is missing error was as far as I got in the dataset.ReadXml(ioTemp) where ioTemp was the System.IO.MemoryStream used in the xml serializer].
Because of the old desktop apps, I've never had to worry about sorting a collection since the gridview handled it once it was loaded. In fact, it's a 'standard' that the collection's .SortProperty, .SortDirection and .ApplySort all through NotSupportedExceptions (I inherited this code from programmers long gone).
Is there an easy way to convert the collection to a data table or a way to sort the collection without having to go back to the database each time? Object Data Sources won't work becuase of the intricate rules in how the objects are built - the wizards in VS2005 just can't handle what we need to do (grabbing data from several tables conditionally to make an object).
Thanks in advance.
Have you considered client side sorting instead?
I have used the jquery tablesorter plugin in the past with ASP Gridviews.
http://tablesorter.com/
I had a similar issue and i needed to implement IComparable on the objects. Basically to sort a collection of objects you need a way to distinguish their order. The IComparable interface has one method called Compare which allows the .Net framework to work out the order of the objects when you sort them. You need to implement this method yourself to get the sort method to work.
Google results
You don't mention the error message so i cant be sure if this is the case, can you post the error?
EDIT :
In regards to your comment; you can implement multi column sorting, it just requires more work. You can specify the fields to sort the collection by and then use this information within the CompareTo Method.
Have a look at this
Given that you apparently are populating the grid with a collection of your own objects, this sounds like a perfect job for Linq for Objects. With just a little elbow grease you can achieve what is effectively an SQL Select statement against your collection. Very cool stuff.
http://www.hookedonlinq.com/LINQtoObjects5MinuteOverview.ashx
Also, do you really just want to sort the data in the grid? If so, then'd definitely pursue using Linq against your objects. However, rarely does sorting the contents of the grid really answer the problem ("sorting the grid" usually translates into changing the access path of the data used to fill the grid.) Browser apps aren't like Windows apps and don't have a full-time connection to the underlying data source to make things happen quite as magically as the DataGridView in Windows makes things seem.
You can put link buttons with an On_Click event as the header's of each column.
When the event is triggered, figure out which header was clicked on (one method per header or a commandArgument value). Once that is know, do a .orderBy or .OrderByDescending by on the collection of objects, and put the result back in as datasource of the gridview and databind on that.
In the year since I originally asked this question, I managed to get a new 'standard' implemented so that collections of business objects were now generic lists.
So now a "Collection class" that is little more than a "Inherits List(Of MyBusinessObject)" with a Sort Method that looks like this (performance wasn't an issue):
Public Overloads Sub Sort(ByVal strPropertyName As String, ByVal strDirection As String)
Dim arSortedList As New ArrayList
For Each item As MyBusinessObject In Me
arSortedList.Add(item)
Next
arSortedList.Sort(New CaseInsensitiveComparer(Of MyBusinessObject)(strPropertyName, strDirection))
For intI As Integer = 0 To arSortedList.Count - 1
Item(intI) = arSortedList(intI)
Next
End Sub
This seemed to work perfectly with the methodology used by the GridView for firing events.