How to serialize & deserialize user inputs gathered from inside a panel? - c#

I'm creating an winforms application, that has the user make inputs in different panels. I already wrote a method to traverse through the panel and get the inputs from the different Controls. Now I need to find a way to serialize these inputs and deserialize them later on, so that all inputs are again in the right Controls (e.g. "Jack" is again in the TextBox "tbName").
I thought of multiple solutions, e.g. creating a list for each panel, which serializes to a .txt with a structure similiar to "tbName=Jack" and so on. But I don't really know how I would deserialize that, without traversing both my panel controls and the list again. Or can I possibly serialize the whole Panel object together with the Child-Controls?
//This is the method I use to gather the inputs from the panels.
public IEnumerable<Control> GetControls(Control parentControl)
{
foreach (Control child in parentControl.Controls)
{
yield return child;
foreach (Control controlChild in GetControls(child))
{
yield return controlChild;
}
}
}

It's not advised to serialize the whole form, as it has a lot of information you don't need (and t hat may affect performance). Instead, create a separate class, make it [Serializable()], make all the variables you need to store your information, and serialize that class.
EDIT:
Say you have the following form:
namespace Test
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
// here, you create the serializing and deserializing methods
public void SerializingInfo()
{
// done however you see fit
}
public StorageClass DeserializingInfo()
{
// also done however you see fit
}
}
}
Then, add another class to your project, which in my example, is named StorageClass.
This will look like:
namespace Test
{
[Serializable()]
public class StorageClass
{
// has all your properties
}
}
Then, whatever you need to storage, you can do so by setting/getting the properties in the Form1. When you serialize it, all the properties are serialized together, and you can retrieve it by accessing their getter method in DeserializeInfo().

For a LIMITED number of controls, you could simply create Settings in your Project --> Properties for each one:
Then, in the ApplicationSettings property for your control, click the three dots to the right of PropertyBinding...
...and select the setting for the Text entry:
You'll now have this:
Finally, in the FormClosing() event of the form, save the settings:
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
Properties.Settings.Default.Save();
}

Thanks for the answers, both are correct and working, but in the end I figured out my own solution to the problem:
I made a seperate class with all the attributes I needed, just like #krobelusmeetsyndra suggested, and made a generic List of the class object i just made. Then I traversed through the controls (with the method from my question), put the data in the List and serialized that data with the XmlSerializer.
Same with deserializing: I made a List of my own object type, then loaded the data from the XML in that list, and then assigned it to the right controls.
Hope that helps everyone with the same question!

Related

WPF: property similar to WinForms Modified

In WinForms controls like a TextBox have property Modified that gets value "true" after changing the control's content and may be set to "false" manually. Their WPF analogues seem not to have such property (neither IsModified in new naming style). So do I have to handle their modifying events myself or there's some more convenient way?
For example I have few textboxes and a function, which combines their contents into one document for preview. Opening the preview I want to keep an old content for the document, if none of the textboxes was changed or to call the function to produce new document's content if at least one textbox was edited.
In WPF it's easier to control everything through ViewModel/Model... This might be too much/not what you're looking for. But through experience, I feel that the pattern below pays off in easy usage.
Wrap your simple data class (with all the properties that it is using now/in your question now) in a class/Model that implements IEditableObject, INotifyPropertyChanged and possibly IEquitable. Lets call your class Data.
In a wrapper class create fields:
Data _current;
Data _proposed;
Data _previous;
IEditableObject requires you to implement BeginEdit(), EndEdit() and CancelEdit().
in them you need to control the state _current, proposed, and previous. For example,
public void CancelEdit()
{
_current = _previous;
_proposed = null;
}
public void EndEdit()
{
_previous = _proposed;
}
public void BeginEdit()
{
_proposed = _current;
}
You might need more logic in methods above, so this is just an example. The key of knowing if your object has changes is implementing a flag, lot's of people call it IsDirty:
pubic bool IsDirty { get { return _current != _previous; } }
Now the user of this class can easily check the state. Oh, and on more thing each property would have the following mechanism:
public string Example
{
get { return _current.Example;}}
set
{
if(_current.Example == value) return;
BeginEdit();
_current.Example = value;
RaisePropertyChanged (() -> Example);
}
}
What's nice about implementing IEditableObject, all controls respond to it, DataGrid is a good example and also you can easily return to the original state by cancelling edit.
Anyway, there are lots of samples that you should browse for. I just hope to can get you started onto that path...
P.S. this pattern was used before WPF came out, its super common in WinForms as well
WPF doesn't have that because UI is not Data and therefore your UI is not the right place to store information about whether your data has changed or not.
Crappy dinosaur winforms doesn't allow a clean and true separation between UI and application logic/data and therefore has all sorts of horrible hacks in order to mash together these completely separate concepts.
You must learn to develop correctly, using the MVVM pattern. Then you will realize there's no sense in placing state data on any UI elements.

How to add properties to a Form on a group, and show in Properties Windows and be editable

I have this class:
public class MyProps
{
public MyProps()
{
}
protected string myVar;
public string MyProperty
{
get { return myVar; }
set { myVar = value; }
}
protected int myOtherVar;
public int MyOtherProperty
{
get { return myOtherVar; }
set { myOtherVar = value; }
}
}
That I want to add to my Form, so when I inherit from it I will be able to fill the properties in the MyPropsX property.
I have this code in my form:
protected MyProps propsX = new MyProps();
[TypeConverter(typeof(ExpandableObjectConverter))]
public MyProps MyPropsX
{
get
{
return propsX;
}
set
{
propsX = value;
}
}
Now, the properties MyProperty and MyOtherProperty are nicely shown in the Properties Window, and I can set their values directly there.
But when I close my form and I open it again, all my changes are lost, the properties being reset to show zero and an empty string.
What am I missing?
Should I inherit my MyProps class from certain special class or interfase?
Or some special attribute?
This is a little bit much for a comment and maybe your solution, so i'm answering to your comment with an answer instead with another comment:
With does not happen when I put properties directly on a form you mean, you are using the designer to set some property of the form. These will be written into the MyForm.designer.cs file. When you go into the code of your class you'll find within the constructor a method InitializeComponent(). Set the cursor on it an press F12. Here you can see what the designer has written into all the properties. You should respect the comment above the mentioned method and not start to modify the code with the code editor unless you really have understand how and when the designer will read and write code here (which is another chapter i can explain if needed). Otherwise it will happen that trying to opening your form with the designer after the code change will lead to an error message or code loss.
If you like to set some default value also, you should go back into the constructor and add the needed initialization code below the InitializeComponent() function and everything should work as expected.
Update
As you wrote in your comment you already know how the Designer interacts with the *.designer.cs file. So i really can't understand your concrete problem but maybe one of these articles can give you a more insight about how Microsoft wrote their components:
Make Your Components Really RAD with Visual Studio .NET Property Browser
Components in Visual Studio
This is very normal, since each time you are closing the form and opening it again you are having a new instance from the form MyPropsX, so the best way would be to save your properties in any kind of a database (sql, access, textfiles,...)

IHierarchyData and IHierarchicalEnumerable in Winforms

Currently,I know how to do a lazy implementation of the loading procedure of the nodes in a treeview control, and read the related questions in stackoverflow, but I'm also reading about IHierarchyData and IHierarchicalEnumerable interfaces in asp.net (I didn't know to code asp.net) that allow to bind a collection to a treeview in order to display the items automatically.
It would like to know if I can do the same in winforms and C#. I think that the interfaces previous mentioned are not available in winforms.
Thanks.
The Windows Forms TreeView does not know how to bind to an IHierarchyData instance, which isn't surprising given that the IHierarchyData and related interfaces are intended for consumption by web controls (especially site maps).
However, it's really not too hard to build your own data binding class. This seemed like an interesting problem so I threw one together just for fun. I'll walk you through the inner workings.
First, create a basic Component class. Visual Studio will start you off with code like this:
public partial class TreeViewHierarchyBinding : Component
{
public TreeViewHierarchyBinding()
{
InitializeComponent();
}
public TreeViewHierarchyBinding(IContainer container)
{
container.Add(this);
InitializeComponent();
}
}
One obvious piece of "state" this component needs to have is a mapping from each TreeNode to its IHierarchyData. Now we can hack around this by throwing it in the TreeNode's Tag property, but let's aim to make this component as non-invasive as possible and keep track of its own state. Hence, we'll use a dictionary. Add this field to the class:
private Dictionary<TreeNode, IHierarchyData> nodeDictionary = new
Dictionary<TreeNode, IHierarchyData>();
Now, at a minimum, this component needs to know how to populate a specific parent TreeNode of a TreeView class from its correspondingly bound IHierarchyData, so let's write that code next:
private void PopulateChildNodes(TreeNodeCollection parentCollection,
IHierarchicalEnumerable children)
{
parentCollection.Clear();
foreach (object child in children)
{
IHierarchyData childData = children.GetHierarchyData(child);
TreeNode childNode = new TreeNode(childData.ToString());
if (childData.HasChildren)
{
childNode.Nodes.Add("Dummy"); // Make expandable
}
nodeDictionary.Add(childNode, childData);
parentCollection.Add(childNode);
}
}
private void UpdateRootNodes(TreeView tv, IHierarchyData hierarchyData)
{
if (tv == null)
{
return;
}
tv.Nodes.Clear();
if (hierarchyData != null)
{
IHierarchicalEnumerable roots = hierarchyData.GetChildren();
PopulateChildNodes(tv.Nodes, roots);
}
}
This part should be pretty straightforward. The first method just populates a TreeNodeCollection (i.e. the Nodes property of a TreeNode) with the hierarchy obtained from an IHierarchyData instance, using the IHierarchyEnumerable interface. The only really interesting things this method does are:
Adding a dummy node when the IHierarchyData instance has children; this makes the "+" visible in the tree view, otherwise we wouldn't be able to expand any deeper; and
Adding the newly-added node to the dictionary with the IHierarchyData instance it matches with.
The second method is even simpler, it does the initial "binding work", replacing whatever is in the root of the tree with our top-level IHierarchyData instance.
The next thing our component needs to be able to do is hook the loading events from the TreeView to perform lazy-loading. Here's the code to do that:
private void RegisterEvents(TreeView tv)
{
tv.BeforeExpand += TreeViewBeforeExpand;
}
private void UnregisterEvents(TreeView tv)
{
tv.BeforeExpand -= TreeViewBeforeExpand;
}
private void TreeViewBeforeExpand(object sender, TreeViewCancelEventArgs e)
{
if (e.Node.Checked)
{
return;
}
IHierarchyData hierarchyData;
if (nodeDictionary.TryGetValue(e.Node, out hierarchyData))
{
PopulateChildNodes(e.Node.Nodes, hierarchyData.GetChildren());
e.Node.Checked = true;
}
}
First two methods should be self-explanatory, and the third method is the actual lazy-loading code. We're cheating a little here, using the TreeNode.Checked property to delineate whether or not the child nodes have already been loaded so we don't do any unnecessary reloads. I always do this when I implement lazy-loaded trees because, in my experience, I almost never use the TreeNode.Checked property. However, if you do need to use this property for something else, you can either use a different property (like Tag), create another dictionary to hold the expanded states, or modify the existing dictionary to hold a composite class (containing the IHierarchyData as well as an Expanded property). I'm keeping it simple for now.
The rest should already make sense to you if you've implemented lazy-loading in a tree before, so let's skip ahead. Really the only thing left to do at this point is implement some designer/user properties that will actually wire up the tree and data:
private IHierarchyData dataSource;
private TreeView treeView;
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public IHierarchyData DataSource
{
get { return dataSource; }
set
{
if (value != dataSource)
{
dataSource = value;
nodeDictionary.Clear();
UpdateRootNodes(treeView, value);
}
}
}
[Category("Behavior")]
[DefaultValue(null)]
[Description("Specifies the TreeView that the hierarchy should be bound to.")]
public TreeView TreeView
{
get { return treeView; }
set
{
if (value != treeView)
{
if (treeView != null)
{
UnregisterEvents(treeView);
}
treeView = value;
nodeDictionary.Clear();
RegisterEvents(value);
UpdateRootNodes(treeView, dataSource);
}
}
}
Easy peasy. We've got a DataSource property that accepts the root IHierarchyData, and a TreeView property which you'll be able to access from the designer. Again, simple stuff here, when the DataSource property is updated, we just reset the lookup and repopulate the root. When the TreeView property is updated we have to do a little more work, registering the events, making sure to unregister events from the old tree view, and doing all the same stuff we do when the data source changes.
That's really all there is to it! Open up the Windows Forms designer, drop a TreeView, then drop a TreeViewHierarchyBinding and set its TreeView property to the tree view you just dropped. Finally, in your code somewhere (i.e. in the Form_Load event), give it a data source:
private void Form1_Load(object sender, EventArgs e)
{
DirectoryInfo dir = new DirectoryInfo("C:\\");
treeViewHierarchyBinding1.DataSource = new FileSystemHierarchyData(dir);
}
(Note - this uses the example FileSystemHierarchyData that's on the MSDN page for IHierarchyData. The example isn't very robust, it doesn't check for UnauthorizedAccessException or anything, but it's good enough to demonstrate this).
And that's it. Run your app and watch it bind. You can now reuse the TreeViewHierarchyBinding component anywhere - just drop it on a form, assign it a TreeView, and give it an IHierarchyData instance as a data source.
I've put the complete code on PasteBin if you want a copy-and-paste version.
Have fun!
The interfaces are available, but will require you to add a reference to System.Web.UI. (It might also require you to use the full .NET Framework redistributable rather than the Client Profile, although I'm not certain about that.)
The larger question is: Does the WinForms TreeView control automatically understand how to work with these interfaces? I believe the answer to that question is "No", but you would need to test/verify that.
There's an interesting article here that shows you how to build extension methods to achieve what I think you're looking for. There is no native availability within the System.Windows.Forms.TreeView to bind to a collection from what I can find.
You CAN include System.Web.UI in your project to make the IHierarchyData and IHierarchicalEnumerable interfaces available, but the TreeView will not be able to attach to them without the extension methods.
The sample source code from the web site will let you bind any IDictionary collection to the TreeView.

Dynamic options dialog (using reflection)

Does anyone know of a good component (C# WinForms) which would allow creating an options (settings) form, given a custom class with a bunch of properties? I am not looking for something shiny, but something merely better than a property grid. I can easily take care of the visual part, but I simply don't want to lose time doing reflection to add and bind controls if it already exists.
I am pretty sure I've seen a Visual Studio options-like form somewhere before, which was created dynamically (with some attributes attached to the properties of the class, to allow grouping and additional info).
[Edit] For example, I might have an options class:
public class Options : SerializableOptions<Options>
{
[Category("General")]
[Name("User name")]
[Description("Some text")]
public string Username { get; set; }
[Category("General")]
[Name("Log in automatically")]
public bool LogInAutomatically { get; set; }
[Category("Advanced")]
// ConnectionType is enum
public ConnectionType ConnectionType { get; set; }
// ...
}
After passing it to this form, it would create two panels ("General" and "Advanced"), with a CheckBox and a TextBox on the first panel, and one ComboBox (with all available enums) on the second panel.
If there isn't such a control, what do you guys use? Manually add, populate, format and bind controls for each option?
I'm not aware of any controls that allow you to do this, but it isn't difficult to do yourself. The easiest way is to create the dialog shell, a user control which acts as the base class for the options "panels", one (or more) attribute to control the name and grouping information, and an interface (which the user control implements).
Each of your custom options panels derives from the user control and overrides some sort of Initialize() and Save() method (provided by the user control). It also provides your attribute (or attributes) that determine the name/grouping information.
In the dialog shell, reflectively inspect all public types from your assembly (or all loaded assemblies) looking for types that implement your interface. As you find a type, get the attributes to determine where to place it in your grouping (easiest thing here is to use a tree view), call Activator.CreateInstance to create an instance of the user control and store it in the Tag property. When the user clicks on an entry in the grouping (a tree node), get the Tag and set the panel which contains the user control to the object in the Tag property. Finally, when the user clicks "OK" on the dialog, loop through the tree nodes, get the Tag property and call the Save method.
Update:
Another option would be to use a property grid control. It doesn't have a "pretty" UI look to it, but it is very functional, already supports grouping by a category attribute, and allows a great deal of flexibility. You could go with a single property grid that shows all of the options, or go with a "hybrid" approach with a tree view that groups by major functions (plugin, capability, etc.), probably based on the type. When the user clicks that node, give the property grid the object instance. The only drawback to this approach is that when changes are made to the property grid values they are "live" in that the underlying property is immediately changed, which means there is no concept of "Cancel" short of saving a copy of each value that could change and performing some type of "reset" yourself.
I don't know if such a control exists, but writing the required reflection code is really not that hard. E.g. something like this:
// the class for which to create an UI
public class MyClass
{
public string Text { get; set; }
public int ID { get; set; }
}
...
// basic reflection code to build the UI for an object
var obj = new MyClass() { Text="some text", ID=3};
foreach (var pi in obj.GetType().GetProperties())
{
var name = pi.Name;
var type = pi.PropertyType;
var value = pi.GetValue(obj, null);
//now setup the UI control for this property and display the value
}
I accidentally found something similar to this, I remebered that I had this problem a while ago and thought I should share it.
Here is a simple example: http://blog.denouter.net/2008/08/simple-reflection-form.html. It uses reflection to create several controls based on object's properties.

Subclassed form not behaving properly in Designer view (VS 2008)

I have subclassed Form to include some extra functionality, which boils down to a List<Image> which displays in a set of predefined spots on the form. I have the following:
public class ButtonForm : Form
{
public class TitleButton
{
public TitleButton() { /* does stuff here */ }
// there's other stuff too, just thought I should point out there's
// a default constructor.
}
private List<TitleButton> _buttons = new List<TitleButton>();
public List<TitleButton> TitleButtons
{
get { return _buttons; }
set { _buttons = value; }
}
// Other stuff here
}
Then my actual form that I want to use is a subclass of ButtonForm instead of Form. This all works great, Designer even picks up the new property and shows it up on the property list. I thought this would be great! It showed the collection, I could add the buttons into there and away I would go. So I opened the collection editor, added in all the objects, and lo and behold, there sitting in the designer was a picture perfect view of what I wanted.
This is where it starts to get ugly. For some reason or another, Designer refuses to actually generate code to create the objects and attach them to the collection, so while it looks great in Design mode, as soon as I compile and run it, it all disappears again and I'm back to square one. I'm at a total loss as to why this would happen; if the Designer can generate it well enough to get a picture perfect view of my form with the extra behaviour, why can't/won't it generate the code into the actual code file?
First of all you need to inherit your TitleButton class from Component so that the designer knows it is a component that can be created via designer generated code. Then you need to instruct the designer code generator to work on the contents of the collection and not the collection instance itself. So try the following...
public class TitleButton : Component
{
// ...
}
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public List<TitleButton> TitleButtons
{
// ...
}

Categories