data binding non-strings to TextBox.Text (WinForms) - c#

I'm storing a whole bunch of settings for my winforms program in various .settings files.
Included in these settings are System.Drawing.Point, System.Drawing.Location, System.TimeSpan, System.DateTime, etc...
When I bind a form's Location to a System.Drawing.Location, and call the .Save() method, it all seems to work fine. That is, since there doesn't seem to be a need for a cast, the form's System.Drawing.Location is directly compatible with the System.Drawing.Location setting stored in the .settings file.
Additionally, if I say TimeSpan timeSpan = Settings.Duration; that also works fine.
Now, I have made a large settings form, where the user can adjust various parameters, including the various DateTime and TimeSpan settings. These are visible and editable in many TextBox, which I have data bound in the following way:
Settings DefaultSettings = Settings.Default;
TextBox1.DataBindings.Add(("Text", DefaultSettings, "Duration", false, DataSourceUpdateMode.OnValidation, new TimeSpan(00, 30, 00));
The TimeSpan is visible in the TextBox, however when I try to edit it and call Save() on the settings data source, I get the following error:
Value of '0' is not valid for 'Value'. 'Value' should be between 'Minimum' and 'Maximum'.
Parameter name: Value
The error originates from the Visual Studio generated code block:
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("00:30:00")]
public global::System.TimeSpan StartWorkDay {
get {
return ((global::System.TimeSpan)(this["Duration"]));
}
set {
this["Duration"] = value;
}
}
I think the problem is caused by the fact that it's trying to turn a string into a System.TimeSpan, and the various other classes I have in my .settings file.
Since I'm binding them using the DataBindings.Add( which accepts strings for the parameters, I can't cast or use the new keyword there.
I could handle it all manually in code: updating the settings file by constructing the objects parameter-by-parameter, but I have so many settings stored in so many TextBoxes and NumericUpDowns that I would prefer to just bind them straight to the TextBox, assuming that's possible.
What is the easiest way I can achieve this?

You could declare a “conversion property” (I just invented that term) in the Settings class:
public class Settings
{
// The real property
public TimeSpan StartWorkDay { get; set; }
// The conversion property
public string StartWorkDayString
{
get
{
return StartWorkDay.ToString();
// (or use .ToString("...") to format it)
}
set
{
StartWorkDay = TimeSpan.Parse(value);
// (or use TryParse() to avoid throwing exceptions)
}
}
}
... and then bind the textbox to that.

I tried the following code in a sample windows form application and it did the save without any exceptions.
// Define the settings binding source
private System.Windows.Forms.BindingSource settingsBindingSource;
private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
this.durationTextBox = new System.Windows.Forms.TextBox();
this.settingsBindingSource = new BindingSource(this.components);
this.settingsBindingSource.DataSource = typeof(WindowsFormsApplication1.Properties.Settings);
this.durationTextBox.DataBindings.Add(new Binding("Text", this.settingsBindingSource, "Duration", true));
}
// In the form load event where the textbox is displayed
private void Form1_Load(object sender, System.EventArgs e)
{
settingsBindingSource.DataSource = Settings.Default;
}
// save button click
private void button1_Click_1(object sender, System.EventArgs e)
{
// This saved the settings, without any exceptions
Settings.Default.Save();
}
Hope it helps.

I came up with a somewhat-elegant solution which you might be interested in:
Since the problem seemed to be that I was unable to cast or construct the data types I had in the .settings file from simple text boxes as I was using data binding, I instead made some custom controls.
For example, the TimeSpans now use a TimeSpanPicker which I made out of a DateTimePicker control, with the date disabled, the up/down toggles on, and the Value property is converted to a TimeSpan from within the picker control.
The added advantage of this method is that I don't need to do a lot of the validation I needed to before with text boxes, as the TimeSpanPicker base control DateTimePicker will only show valid times. What little validation I do need to do can be done in the Set{} property, so I don't need to define event handlers.
This seems to be working well!
Now all I need to do is replace all the text boxes with custom controls.

Related

WPF C# - Saving a Canvas with Dynamic Devices with Code Behind properties (variables)

We've searched all over stack overflow and similar sites for something that will work for our app, but everything gets us only halfway there.
We have an application that allows the user to drag and drop devices onto a drop canvas. Upon the device being dropped, their "router properties" are created, and you can change their name, address, add notes.
We also let the user connect lines between the devices. (We also add the router properties that are created to an observable collection).
We have tried xmlserialization, and it let us save the physical side of the device, but upon loading the xml file, it no longer has the address, notes, etc attached to any saved device, and doesn't allow for adding connections or going to its properties.
I realize that we need to somehow serialize the code behind, then add it back in to each device upon de-serializing, but we can't seem to find a way to serialize the observable collection of router properties.
Does anyone have any suggestions on the simplest way to allow us to save the canvas, children, and their code behind properties? I am attaching pictures for reference, the router properties class and I'm happy to include any code if needed. We really appreciate any help at all.
Warm Regards,
Tyler
For example
Class
public class RouterProperties : INotifyPropertyChanged
{
private ArrayList incomingConnections = new ArrayList();
private ArrayList outgoingCnnections = new ArrayList();
private bool isLocked = true;
private bool isSelected = false;
private string deviceName = "Router";
private string hostName = "Host name";
private string routerIP = "192.168.0.1";
private string note = "Notes";
private string status = "Yellow";
private BitmapImage icon;
// getters and setters removed for brevity
public ArrayList IncomingConnections
...
public ArrayList OutgoingCnnections
...
public bool IsLocked
...
public bool IsSelected
...
public string DeviceName
...
public string HostName
...
public string RouterIP
...
public string Note
...
public string Status
...
public BitmapImage Icon
...
MainWindow Class
public ObservableCollection<RouterProperties> devices = new ObservableCollection<RouterProperties>();
EDIT Code to save xaml
// De-Serialize XML to UIElement using a given filename.
public static UIElement DeSerializeXAML(string filename)
{
// Load XAML from file. Use 'using' so objects are disposed of properly.
using (System.IO.FileStream fs = System.IO.File.Open(filename, System.IO.FileMode.Open, System.IO.FileAccess.Read))
{
return System.Windows.Markup.XamlReader.Load(fs) as UIElement;
}
}
// Serializes any UIElement object to XAML using a given filename.
public static void SerializeToXAML(UIElement element, string filename)
{
// Use XamlWriter object to serialize element
string strXAML = System.Windows.Markup.XamlWriter.Save(element);
// Write XAML to file. Use 'using' so objects are disposed of properly.
using (System.IO.FileStream fs = System.IO.File.Create(filename))
{
using (System.IO.StreamWriter streamwriter = new System.IO.StreamWriter(fs))
{
streamwriter.Write(strXAML);
}
}
}
private void menuSave_Click(object sender, RoutedEventArgs e)
{
Microsoft.Win32.SaveFileDialog dlg = new Microsoft.Win32.SaveFileDialog();
dlg.FileName = "UIElement File"; // Default file name
dlg.DefaultExt = ".xaml"; // Default file extension
dlg.Filter = "Xaml File (.xaml)|*.xaml"; // Filter files by extension
// Show save file dialog box
Nullable<bool> result = dlg.ShowDialog();
// Process save file dialog box results
if (result == true)
{
// Save document
string filename = dlg.FileName;
SerializeToXAML(canvasMain, filename);
}
}
private void menuLoad_Click(object sender, RoutedEventArgs e)
{
Microsoft.Win32.OpenFileDialog dlg = new Microsoft.Win32.OpenFileDialog();
dlg.DefaultExt = ".xaml"; // Default file extension
dlg.Filter = "Xaml File (.xaml)|*.xaml"; // Filter files by extension
// Show open file dialog box
Nullable<bool> result = dlg.ShowDialog();
// Process open file dialog box results
if (result == true)
{
string filename = dlg.FileName;
Canvas canvas = DeSerializeXAML(filename) as Canvas;
// Add all child elements (lines, rectangles etc) to canvas
while (canvas.Children.Count > 0)
{
UIElement obj = canvas.Children[0]; // Get next child
canvas.Children.Remove(obj); // Have to disconnect it from result before we can add it
canvasMain.Children.Add(obj); // Add to canvas
}
}
}
Unfortunately i dont see a solve for your current approach, or at least none that has come to mind.
Here are the fundamentals of the problem
Serialization Limitations of XamlWriter.Save
Run-Time, Not Design-Time Representation
The basic philosophy of what is serialized by a call to Save is that
the result will be a representation of the object being serialized, at
run-time. Many design-time properties of the original XAML file may
already be optimized or lost by the time that the XAML is loaded as
in-memory objects, and are not preserved when you call Save to
serialize. The serialized result is an effective representation of the
constructed logical tree of the application, but not necessarily of
the original XAML that produced it. These issues make it extremely
difficult to use the Save serialization as part of an extensive XAML
design surface.
Extension References are Dereferenced
Common references to objects made by various markup extension formats,
such as StaticResource or Binding, will be dereferenced by the
serialization process. These were already dereferenced at the time
that in-memory objects were created by the application runtime, and
the Save logic does not revisit the original XAML to restore such
references to the serialized output. This potentially freezes any
databound or resource obtained value to be the value last used by the
run-time representation, with only limited or indirect ability to
distinguish such a value from any other value set locally. Images are
also serialized as object references to images as they exist in the
project, rather than as original source references, losing whatever
filename or URI was originally referenced. Even resources declared
within the same page are seen serialized into the point where they
were referenced, rather than being preserved as a key of a resource
collection.
My first solution would have been to assign a GUID or id to each control and router property. however seemingly this wont work, XamlWriter.Save just doesn't preserve bindings or things of that nature.
However i think you need to attack this from a ViewModel first approach
That's to say, that your ViewModel needs to keep all the implementation properties of your visual objects, the locations and anything needed to rebuild your canvas visually. As you create each visual router you need to keep all of its relevant state somewhere
Even if the implementation details are separate from the the Router ViewModel you could serialize them both and have some sort of ID to relink them at runtime.
Though my Spidey senses tells me you should redesign the architecture a bit to put all the relevant in a single Higher-Level ViewModel, though this really all depends on what the architecture of the application is.
Maybe you could have a structure like this
[Serializable]
public class RouterAndState
{
public RouterProperties {get;set;}
Public RouterVisualState {get;set;}
}
[Serializable]
public class RouterVisualState
{
// its location (x,y) and anything else it needs to be recreated
}
If you are saving the router properties to a db the router entity really doesn't care what the visual layout of the canvas is, and its not something that really should be saved but maybe it can be saved in a related table that has a map to the routers used and a map to its layout, Ie RouterMap Table, with foreign keys to the RouterProperties and Visual Layout Configuration
The other way is to just generate the visual state from the routerProperties and auto generate the layout, this is neat but you will need implement a lot more logic to auto configure how its laid-out when loading .
However if this is a fairly simple things, just serialize it all to a file using something like the above and be done with it
I hope this helps

Word interop add-in - changing Window View properties do not affect Revision markup style

Using an application level add-in, I'm performing some operations on document open that require revisions (tracked changes) to be rendered inline and not hidden, so that they are contained within the Range of the document. After consulting the documentation, I thought that all I had to do was change the the view properties of the active window: MarkupMode claims to do what I want.
But this property seems to be completely disconnected from how revisions display in the document! To test this, I tried toggling the mode manually in a document, and looking at MarkupMode, and checking it immediately afterwards in an onSelectionChange event handler. I went ahead and tracked a bunch of properties of ActiveWindow.View, for good measure. To my surprise and chagrin, when I looked at the locals with changes rendered inline:
... and compared the values to those with changes hidden:
Nothing changed! What gives? Am I not looking at the right property/properties to ensure that changes are rendered inline? Is Microsoft completely incapable of writing meaningful documentation? I'll point out that I tried to make the property change in code as well, to see if the rendering of revisions would change, with no success. I would appreciate any feedback.
Edit: Simple code to duplicate issue:
private void ThisAddIn_Startup(object sender, EventArgs e)
{
Application.WindowSelectionChange += application_WindowSelectionChange;
}
private void application_WindowSelectionChange(Selection sel)
{
var testDoc = sel.Document;
var test = new
{
testDoc.ActiveWindow.View,
testDoc.ActiveWindow.View.ShowRevisionsAndComments,
testDoc.ActiveWindow.View.ShowInsertionsAndDeletions,
testDoc.ActiveWindow.View.MarkupMode,
testDoc.ActiveWindow.View.RevisionsMode
};
}
Edit 2: Beyond this contrived example, I need to have control over the markup style of Revisions because I am searching for text on DocumentOpen that may include text that is present as Revision objects. More specifically, I am attempting to do the following, using the above text (with text "powerful way to help you prove " deleted in a revision):
private void ThisAddIn_Startup(object sender, EventArgs e)
{
Application.DocumentOpen += application_DocumentOpen;
}
private void application_DocumentOpen(Document doc)
{
// expected text, as taken from screengrab example above. Includes
// text removed in a revision
string expectedText = "Video provides a powerful way to help you prove your point.";
// make sure that we're in print view
if (doc.ActiveWindow.View.Type != WdViewType.wdPrintView)
{
doc.ActiveWindow.View.Type = WdViewType.wdPrintView;
}
// attempt to ensure that document revisions are marked up inline. Does not accomplish anything
doc.ActiveWindow.View.MarkupMode = WdRevisionsMode.wdInLineRevisions;
// attempt to locate text. Will fail if revisions are not marked up inline (deletion is not part of document content range otherwise)
var locatedRange = doc.Content.OccurrenceOfText(expectedText);
}
// extension method to locate text inside a range. Searching entire Content in this example
private static Range OccurrenceOfText(this Range rng, string text)
{
rng.Find.Forward = true;
rng.Find.Format = false;
rng.Find.Execute(text);
if (!rng.Find.Found)
{
throw new Exception("Unable to locate text! Are Revisions marked up inline?");
}
// return brand new range containing located content
return rng.Document.Range(rng.Start, rng.End);
}
Edit 3: As Cindy made clear, my problem was that I was using the wrong property: I needed to use the View.RevisionsFilter.Markup property to make the change. Additionally, an issue that I hadn't diagnosed is that depending on the View properties, it is entirely possible that the Range returned from a search performed as I do returns a Text property that is different than the text that was searched with. This can happen if Revision objects are present inside the Range.
It works for me. What I did:
Created a test document. Used =rand() ->Press Enter to put some text in the document. Copied in the phrase, changing "powerful" to "useful".
Turned on track changes and made some changes, including selecting "useful" and typing "powerful".
Made sure the changes were displaying in balloons and the "Original" view of the mark-up was being shown.
Saved and closed the document.
Put a break-point on the line that calls OccurrenceOfText, then F5 to start the add-in in debug mode.
Opened the saved document. The Open event fired. I checked the values of the View.Markup and View.RevisionsView - they had changed to inline and "Final". Stepped through the rest of the code and it executed correctly (found the occurrence).
I needed to modify your code slightly since I don't have your "wrapper". I also changed the way OccurrenceOfText returns the Range: no need to do it in the complicated manner you use...
I note that you do not appear to set View.RevisionsView - perhaps this is why you're not seeing the result you expect?
private void Application_DocumentOpen(Microsoft.Office.Interop.Word.Document doc)
{
//Test revisions
// expected text, as taken from screengrab example above. Includes
// text removed in a revision
string expectedText = "Video provides a powerful way to help you prove your point.";
// make sure that we're in print view
if (doc.ActiveWindow.View.Type != Word.WdViewType.wdPrintView)
{
doc.ActiveWindow.View.Type = Word.WdViewType.wdPrintView;
}
// attempt to ensure that document revisions are marked up inline. Does not accomplish anything
Word.View vw = doc.ActiveWindow.View;
vw.MarkupMode = Word.WdRevisionsMode.wdInLineRevisions;
vw.RevisionsView = Word.WdRevisionsView.wdRevisionsViewFinal;
// attempt to locate text. Will fail if revisions are not marked up inline (deletion is not part of document content range otherwise)
var locatedRange = OccurrenceOfText(doc.Content, expectedText);
}
// extension method to locate text inside a range. Searching entire Content in this example
private static Word.Range OccurrenceOfText(Word.Range rng, string text)
{
rng.Find.Forward = true;
rng.Find.Format = false;
rng.Find.Execute(text);
if (!rng.Find.Found)
{
throw new Exception("Unable to locate text! Are Revisions marked up inline?");
}
// return brand new range containing located content
return rng; //.Document.Range(rng.Start, rng.End);
}

WinForm Settings are not saved

I use the VSTO built-in settings file in order to save Windows- application settings.
I have a checkBox on my WinForm, whereas at form load I read its state (checked or unchecked)
from the appropriate property in the settings file.
This works fine as long as I do not exit the application.
However, when I quit and then execute the application again- the settings are not saved from last execution and the checkBox state is not as the last preference.
I use "user" scope to save the settings.
On form load, extract the checkBox state from Settings.
private void MyFormLoad(object sender, EventArgs e)
{
//Find the appropriate property in the Settings file
System.Configuration.SettingsProperty property;
property = P_Settings.Settings.Default.Properties[checkBox.Name];
if (property != null)
checkBox.Checked = Convert.ToBoolean(property.DefaultValue);
}
On form close, Synchronize settings file with form status.
private void ButtonApplyClick(object sender, EventArgs e)
{
System.Configuration.SettingsProperty property;
property = P_Settings.Settings.Default.Properties[checkBox.Name];
property.DefaultValue = checkBox.Checked.ToString();
P_Settings.Settings.Default.Save();
}
I don't think the DefaultValue is where you should store the value for a settings property. That looks more like a part of the property definition (which should stay constant across application runs and thus be hard-coded) rather than like something that will be saved along with the settings.
Instead, try directly using the indexer of the settings object:
Loading:
object propValue = P_Settings.Settings.Default[checkBox.Name];
if (propValue != null) {
checkBox.Checked = Convert.ToBoolean(propValue);
}
Saving:
P_Settings.Settings.Default[checkBox.Name] = checkBox.Checked.ToString();
P_Settings.Settings.Default.Save();
EDIT: Originally, you were storing the checkbox state in the DefaultValue property of the settings property. The DefaultValue is meant to provide a default value that will be returned if no settings value was found in the stored settings. It is not a value that will be stored with the settings, as it is not supposed to be user-defined, or changed during the application's run.
Therefore, what you tried before resulted in the observed behavior: You could assign a value to DefaultValue, which would stay for as long as the application was active, but that value was not stored upon P_Settings.Settings.Default.Save() or restored upon application startup, so upon the next launch of your application, DefaultValue would have its default value (probably null) again.

Winforms preferences values

I'm a beginner in winforms, and just starting using it's preferences.
So, I add in my Settings.settings a Value named path, as string and User Scope.
I change it when I choose a new path with a FolderBrowserDialog and then, after a click on a Ok button, i change the preferences like this :
private void buttonPref_Click(object sender, EventArgs e)
{
Form2 subForm2 = new Form2(textBoxRep.Text);
subForm2.ShowDialog();
if (subForm2.DialogResult == DialogResult.OK)
{
Settings.Default.path= subForm2.rep();
subForm2.Close();
}
else
{
subForm2.Close();
}
}
public string rep()
{
return textBoxRep.Text;
}
Then, when I run my Application, I load the value in my preferences :
textBoxRep.Text = Settings.Default.path;
But, the value is set to empty after every new run.
So I tried with a Application Scope, but I got a read Only error on this : Settings.Default.path
How can I fix this? Is there a way to register the settings after mofified them?
Thank you.
you need to call Save method as below
Settings.Default.path= subForm2.rep();
Settings.Default.Save();
Settings that are application-scoped are read-only, and can only be
changed at design time or by altering the .config file in between
application sessions. Settings that are user-scoped, however, can be
written at run time just as you would change any property value. The
new value persists for the duration of the application session. You
can persist the changes to the settings between application sessions
by calling the Save method.
How To: Write User Settings at Run Time with C#
You need to also call Settings.Default.Save();

Reading default application settings in C#

I have a number of application settings (in user scope) for my custom grid control. Most of them are color settings. I have a form where the user can customize these colors and I want to add a button for reverting to default color settings. How can I read the default settings?
For example:
I have a user setting named CellBackgroundColor in Properties.Settings.
At design time I set the value of CellBackgroundColor to Color.White using the IDE.
User sets CellBackgroundColor to Color.Black in my program.
I save the settings with Properties.Settings.Default.Save().
User clicks on the Restore Default Colors button.
Now, Properties.Settings.Default.CellBackgroundColor returns Color.Black. How do I go back to Color.White?
#ozgur,
Settings.Default.Properties["property"].DefaultValue // initial value from config file
Example:
string foo = Settings.Default.Foo; // Foo = "Foo" by default
Settings.Default.Foo = "Boo";
Settings.Default.Save();
string modifiedValue = Settings.Default.Foo; // modifiedValue = "Boo"
string originalValue = Settings.Default.Properties["Foo"].DefaultValue as string; // originalValue = "Foo"
Reading "Windows 2.0 Forms Programming", I stumbled upon these 2 useful methods that may be of help in this context:
ApplicationSettingsBase.Reload
ApplicationSettingsBase.Reset
From MSDN:
Reload contrasts with Reset in that
the former will load the last set of
saved application settings values,
whereas the latter will load the saved
default values.
So the usage would be:
Properties.Settings.Default.Reset()
Properties.Settings.Default.Reload()
Im not really sure this is necessary, there must be a neater way, otherwise hope someone finds this useful;
public static class SettingsPropertyCollectionExtensions
{
public static T GetDefault<T>(this SettingsPropertyCollection me, string property)
{
string val_string = (string)Settings.Default.Properties[property].DefaultValue;
return (T)Convert.ChangeType(val_string, typeof(T));
}
}
usage;
var setting = Settings.Default.Properties.GetDefault<double>("MySetting");
Properties.Settings.Default.Reset() will reset all settings to their original value.
I've got round this problem by having 2 sets of settings. I use the one that Visual Studio adds by default for the current settings, i.e. Properties.Settings.Default. But I also add another settings file to my project "Project -> Add New Item -> General -> Settings File" and store the actual default values in there, i.e. Properties.DefaultSettings.Default.
I then make sure that I never write to the Properties.DefaultSettings.Default settings, just read from it. Changing everything back to the default values is then just a case of setting the current values back to the default values.
How do I go back to Color.White?
Two ways you can do:
Save a copy of the settings before the user changes it.
Cache the user modified settings and save it to Properties.Settings before the application closes.
I found that calling ApplicationSettingsBase.Reset would have the effect of resetting the settings to their default values, but also saving them at the same time.
The behaviour I wanted was to reset them to default values but not to save them (so that if the user did not like the defaults, until they were saved they could revert them back).
I wrote an extension method that was suitable for my purposes:
using System;
using System.Configuration;
namespace YourApplication.Extensions
{
public static class ExtensionsApplicationSettingsBase
{
public static void LoadDefaults(this ApplicationSettingsBase that)
{
foreach (SettingsProperty settingsProperty in that.Properties)
{
that[settingsProperty.Name] =
Convert.ChangeType(settingsProperty.DefaultValue,
settingsProperty.PropertyType);
}
}
}
}

Categories

Resources