Archive

Posts Tagged ‘Settings’

Adding a WPF Settings Page To The Tools Options Dialog Window For Your Visual Studio Extension

April 25th, 2014 1 comment

I recently created my first Visual Studio extension, Diff All Files, which allows you to quickly compare the changes to all files in a TFS changeset, shelveset, or pending changes (Git support coming soon). One of the first challenges I faced when I started the project was where to display my extension’s settings to the user, and where to save them.  My first instinct was to create a new Menu item to launch a page with all of the settings to display, since the wizard you go through to create the project has an option to automatically add a new Menu item the Tools menu.  After some Googling though, I found the more acceptable solution is to create a new section within the Tools -> Options window for your extension, as this will also allow the user to import and export your extension’s settings.

Adding a grid or custom Windows Forms settings page

Luckily I found this Stack Overflow answer that shows a Visual Basic example of how to do this, and links to the MSDN page that also shows how to do this in C#.  The MSDN page is a great resource, and it shows you everything you need to create your settings page as either a Grid Page, or a Custom Page using Windows Forms (FYI: when it says to add a UserControl, it means a System.Windows.Forms.UserControl, not a System.Windows.Controls.UserControl).  My extension’s settings page needed to have buttons on it to perform some operations, which is something the Grid Page doesn’t support, so I had to make a Custom Page.  I first made it using Windows Forms as the page shows, but it quickly reminded me how out-dated Windows Forms is (no binding!), and my settings page would have to be a fixed width and height, rather than expanding to the size of the users Options dialog window, which I didn’t like.

Adding a custom WPF settings page

The steps to create a Custom WPF settings page are the same as for creating a Custom Windows Forms Page, except instead having your settings control inherit from System.Forms.DialogPage (steps 1 and 2 on that page), it needs to inherit from Microsoft.VisualStudio.Shell.UIElementDialogPage.  And when you create your User Control for the settings page’s UI, it will be a WPF System.Windows.Controls.UserControl.  Also, instead of overriding the Window method of the DialogPage class, you will override the Child method of the UIElementDialogPage class.

Here’s a sample of what the Settings class might look like:

using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.InteropServices;
using Microsoft.VisualStudio.Shell;

namespace VS_DiffAllFiles.Settings
{
	[ClassInterface(ClassInterfaceType.AutoDual)]
	[Guid("1D9ECCF3-5D2F-4112-9B25-264596873DC9")]	// Special guid to tell it that this is a custom Options dialog page, not the built-in grid dialog page.
	public class DiffAllFilesSettings : UIElementDialogPage, INotifyPropertyChanged
	{
		#region Notify Property Changed
		/// <summary>
		/// Inherited event from INotifyPropertyChanged.
		/// </summary>
		public event PropertyChangedEventHandler PropertyChanged;

		/// <summary>
		/// Fires the PropertyChanged event of INotifyPropertyChanged with the given property name.
		/// </summary>
		/// <param name="propertyName">The name of the property to fire the event against</param>
		public void NotifyPropertyChanged(string propertyName)
		{
			if (PropertyChanged != null)
				PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
		}
		#endregion

		/// <summary>
		/// Get / Set if new files being added to source control should be compared.
		/// </summary>
		public bool CompareNewFiles { get { return _compareNewFiles; } set { _compareNewFiles = value; NotifyPropertyChanged("CompareNewFiles"); } }
		private bool _compareNewFiles = false;

		#region Overridden Functions

		/// <summary>
		/// Gets the Windows Presentation Foundation (WPF) child element to be hosted inside the Options dialog page.
		/// </summary>
		/// <returns>The WPF child element.</returns>
		protected override System.Windows.UIElement Child
		{
			get { return new DiffAllFilesSettingsPageControl(this); }
		}

		/// <summary>
		/// Should be overridden to reset settings to their default values.
		/// </summary>
		public override void ResetSettings()
		{
			CompareNewFiles = false;
			base.ResetSettings();
		}

		#endregion
	}
}

 

And what the code-behind for the User Control might look like:

using System;
using System.Diagnostics;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Navigation;

namespace VS_DiffAllFiles.Settings
{
	/// <summary>
	/// Interaction logic for DiffAllFilesSettingsPageControl.xaml
	/// </summary>
	public partial class DiffAllFilesSettingsPageControl : UserControl
	{
		/// <summary>
		/// A handle to the Settings instance that this control is bound to.
		/// </summary>
		private DiffAllFilesSettings _settings = null;

		public DiffAllFilesSettingsPageControl(DiffAllFilesSettings settings)
		{
			InitializeComponent();
			_settings = settings;
			this.DataContext = _settings;
		}

		private void btnRestoreDefaultSettings_Click(object sender, RoutedEventArgs e)
		{
			_settings.ResetSettings();
		}

		private void UserControl_LostKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
		{
			// Find all TextBoxes in this control force the Text bindings to fire to make sure all changes have been saved.
			// This is required because if the user changes some text, then clicks on the Options Window's OK button, it closes 
			// the window before the TextBox's Text bindings fire, so the new value will not be saved.
			foreach (var textBox in DiffAllFilesHelper.FindVisualChildren<TextBox>(sender as UserControl))
			{
				var bindingExpression = textBox.GetBindingExpression(TextBox.TextProperty);
				if (bindingExpression != null) bindingExpression.UpdateSource();
			}
		}
	}
}

 

And here’s the corresponding xaml for the UserControl:

<UserControl x:Class="VS_DiffAllFiles.Settings.DiffAllFilesSettingsPageControl"
						 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
						 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
						 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
						 xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
						 xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
						 xmlns:QC="clr-namespace:QuickConverter;assembly=QuickConverter"
						 mc:Ignorable="d" 
						 d:DesignHeight="350" d:DesignWidth="400" LostKeyboardFocus="UserControl_LostKeyboardFocus">
	<UserControl.Resources>
	</UserControl.Resources>

	<Grid>
		<StackPanel Orientation="Vertical">
			<CheckBox Content="Compare new files" IsChecked="{Binding Path=CompareNewFiles}" ToolTip="If files being added to source control should be compared." />
			<Button Content="Restore Default Settings" Click="btnRestoreDefaultSettings_Click" />
		</StackPanel>
	</Grid>
</UserControl>

You can see that I am binding the CheckBox directly to the CompareNewFiles property on the instance of my Settings class; yay, no messing around with Checked events 🙂

This is a complete, but very simple example. If you want a more detailed example that shows more controls, check out the source code for my Diff All Files extension.

A minor problem

One problem I found was that when using a TextBox on my Settings Page UserControl, if I edited text in a TextBox and then hit the OK button on the Options dialog to close the window, the new text would not actually get applied.  This was because the window would get closed before the TextBox bindings had a chance to fire; so if I instead clicked out of the TextBox before clicking the OK button, everything worked correctly.  I know you can change the binding’s UpdateSourceTrigger to PropertyChanged, but I perform some additional logic when some of my textbox text is changed, and I didn’t want that logic firing after every key press while the user typed in the TextBox.

To solve this problem I added a LostKeyboardFocus event to the UserControl, and in that event I find all TextBox controls on the UserControl and force their bindings to update.  You can see the code for this in the snippets above.  The one piece of code that’s not shown is the FindVisualChildren<TextBox> method, so here it is:

/// <summary>
/// Recursively finds the visual children of the given control.
/// </summary>
/// <typeparam name="T">The type of control to look for.</typeparam>
/// <param name="dependencyObject">The dependency object.</param>
public static IEnumerable<T> FindVisualChildren<T>(DependencyObject dependencyObject) where T : DependencyObject
{
	if (dependencyObject != null)
	{
		for (int index = 0; index < VisualTreeHelper.GetChildrenCount(dependencyObject); index++)
		{
			DependencyObject child = VisualTreeHelper.GetChild(dependencyObject, index);
			if (child != null &amp;&amp; child is T)
			{
				yield return (T)child;
			}

			foreach (T childOfChild in FindVisualChildren<T>(child))
			{
				yield return childOfChild;
			}
		}
	}
}

 

And that’s it.  Now you know how to make a nice Settings Page for your Visual Studio extension using WPF, instead of the archaic Windows Forms.

Happy coding!

Saving And Loading A C# Object’s Data To An Xml, Json, Or Binary File

March 14th, 2014 15 comments

I love creating tools, particularly ones for myself and other developers to use.  A common situation that I run into is needing to save the user’s settings to a file so that I can load them up the next time the tool is ran.  I find that the easiest way to accomplish this is to create a Settings class to hold all of the user’s settings, and then use serialization to save and load the class instance to/from a file.  I mention a Settings class here, but you can use this technique to save any object (or list of objects) to a file.

There are tons of different formats that you may want to save your object instances as, but the big three are Binary, XML, and Json.  Each of these formats have their pros and cons, which I won’t go into.  Below I present functions that can be used to save and load any object instance to / from a file, as well as the different aspects to be aware of when using each method.

The follow code (without examples of how to use it) is also available here, and can be used directly from my NuGet Package.

 

Writing and Reading an object to / from a Binary file

  • Writes and reads ALL object properties and variables to / from the file (i.e. public, protected, internal, and private).
  • The data saved to the file is not human readable, and thus cannot be edited outside of your application.
  • Have to decorate class (and all classes that it contains) with a [Serializable] attribute.
  • Use the [NonSerialized] attribute to exclude a variable from being written to the file; there is no way to prevent an auto-property from being serialized besides making it use a backing variable and putting the [NonSerialized] attribute on that.
/// <summary>
/// Functions for performing common binary Serialization operations.
/// <para>All properties and variables will be serialized.</para>
/// <para>Object type (and all child types) must be decorated with the [Serializable] attribute.</para>
/// <para>To prevent a variable from being serialized, decorate it with the [NonSerialized] attribute; cannot be applied to properties.</para>
/// </summary>
public static class BinarySerialization
{
	/// <summary>
	/// Writes the given object instance to a binary file.
	/// <para>Object type (and all child types) must be decorated with the [Serializable] attribute.</para>
	/// <para>To prevent a variable from being serialized, decorate it with the [NonSerialized] attribute; cannot be applied to properties.</para>
	/// </summary>
	/// <typeparam name="T">The type of object being written to the XML file.</typeparam>
	/// <param name="filePath">The file path to write the object instance to.</param>
	/// <param name="objectToWrite">The object instance to write to the XML file.</param>
	/// <param name="append">If false the file will be overwritten if it already exists. If true the contents will be appended to the file.</param>
	public static void WriteToBinaryFile<T>(string filePath, T objectToWrite, bool append = false)
	{
		using (Stream stream = File.Open(filePath, append ? FileMode.Append : FileMode.Create))
		{
			var binaryFormatter = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
			binaryFormatter.Serialize(stream, objectToWrite);
		}
	}

	/// <summary>
	/// Reads an object instance from a binary file.
	/// </summary>
	/// <typeparam name="T">The type of object to read from the XML.</typeparam>
	/// <param name="filePath">The file path to read the object instance from.</param>
	/// <returns>Returns a new instance of the object read from the binary file.</returns>
	public static T ReadFromBinaryFile<T>(string filePath)
	{
		using (Stream stream = File.Open(filePath, FileMode.Open))
		{
			var binaryFormatter = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
			return (T)binaryFormatter.Deserialize(stream);
		}
	}
}

 

And here is an example of how to use it:

[Serializable]
public class Person
{
	public string Name { get; set; }
	public int Age = 20;
	public Address HomeAddress { get; set;}
	private string _thisWillGetWrittenToTheFileToo = "even though it is a private variable.";

	[NonSerialized]
	public string ThisWillNotBeWrittenToTheFile = "because of the [NonSerialized] attribute.";
}

[Serializable]
public class Address
{
	public string StreetAddress { get; set; }
	public string City { get; set; }
}

// And then in some function.
Person person = new Person() { Name = "Dan", Age = 30; HomeAddress = new Address() { StreetAddress = "123 My St", City = "Regina" }};
List<Person> people = GetListOfPeople();
BinarySerialization.WriteToBinaryFile<Person>("C:\person.bin", person);
BinarySerialization.WriteToBinaryFile<List<People>>("C:\people.bin", people);

// Then in some other function.
Person person = BinarySerialization.ReadFromBinaryFile<Person>("C:\person.bin");
List<Person> people = BinarySerialization.ReadFromBinaryFile<List<Person>>("C:\people.bin");

 

Writing and Reading an object to / from an XML file (Using System.Xml.Serialization.XmlSerializer in the System.Xml assembly)

  • Only writes and reads the Public properties and variables to / from the file.
  • Classes to be serialized must contain a public parameterless constructor.
  • The data saved to the file is human readable, so it can easily be edited outside of your application.
  • Use the [XmlIgnore] attribute to exclude a public property or variable from being written to the file.
/// <summary>
/// Functions for performing common XML Serialization operations.
/// <para>Only public properties and variables will be serialized.</para>
/// <para>Use the [XmlIgnore] attribute to prevent a property/variable from being serialized.</para>
/// <para>Object to be serialized must have a parameterless constructor.</para>
/// </summary>
public static class XmlSerialization
{
	/// <summary>
	/// Writes the given object instance to an XML file.
	/// <para>Only Public properties and variables will be written to the file. These can be any type though, even other classes.</para>
	/// <para>If there are public properties/variables that you do not want written to the file, decorate them with the [XmlIgnore] attribute.</para>
	/// <para>Object type must have a parameterless constructor.</para>
	/// </summary>
	/// <typeparam name="T">The type of object being written to the file.</typeparam>
	/// <param name="filePath">The file path to write the object instance to.</param>
	/// <param name="objectToWrite">The object instance to write to the file.</param>
	/// <param name="append">If false the file will be overwritten if it already exists. If true the contents will be appended to the file.</param>
	public static void WriteToXmlFile<T>(string filePath, T objectToWrite, bool append = false) where T : new()
	{
		TextWriter writer = null;
		try
		{
			var serializer = new XmlSerializer(typeof(T));
			writer = new StreamWriter(filePath, append);
			serializer.Serialize(writer, objectToWrite);
		}
		finally
		{
			if (writer != null)
				writer.Close();
		}
	}

	/// <summary>
	/// Reads an object instance from an XML file.
	/// <para>Object type must have a parameterless constructor.</para>
	/// </summary>
	/// <typeparam name="T">The type of object to read from the file.</typeparam>
	/// <param name="filePath">The file path to read the object instance from.</param>
	/// <returns>Returns a new instance of the object read from the XML file.</returns>
	public static T ReadFromXmlFile<T>(string filePath) where T : new()
	{
		TextReader reader = null;
		try
		{
			var serializer = new XmlSerializer(typeof(T));
			reader = new StreamReader(filePath);
			return (T)serializer.Deserialize(reader);
		}
		finally
		{
			if (reader != null)
				reader.Close();
		}
	}
}

 

And here is an example of how to use it:

public class Person
{
	public string Name { get; set; }
	public int Age = 20;
	public Address HomeAddress { get; set;}
	private string _thisWillNotGetWrittenToTheFile = "because it is not public.";

	[XmlIgnore]
	public string ThisWillNotBeWrittenToTheFile = "because of the [XmlIgnore] attribute.";
}

public class Address
{
	public string StreetAddress { get; set; }
	public string City { get; set; }
}

// And then in some function.
Person person = new Person() { Name = "Dan", Age = 30; HomeAddress = new Address() { StreetAddress = "123 My St", City = "Regina" }};
List<Person> people = GetListOfPeople();
XmlSerialization.WriteToXmlFile<Person>("C:\person.txt", person);
XmlSerialization.WriteToXmlFile<List<People>>("C:\people.txt", people);

// Then in some other function.
Person person = XmlSerialization.ReadFromXmlFile<Person>("C:\person.txt");
List<Person> people = XmlSerialization.ReadFromXmlFile<List<Person>>("C:\people.txt");

 

Writing and Reading an object to / from a Json file (using the Newtonsoft.Json assembly in the Json.NET NuGet package)

  • Only writes and reads the Public properties and variables to / from the file.
  • Classes to be serialized must contain a public parameterless constructor.
  • The data saved to the file is human readable, so it can easily be edited outside of your application.
  • Use the [JsonIgnore] attribute to exclude a public property or variable from being written to the file.
/// <summary>
/// Functions for performing common Json Serialization operations.
/// <para>Requires the Newtonsoft.Json assembly (Json.Net package in NuGet Gallery) to be referenced in your project.</para>
/// <para>Only public properties and variables will be serialized.</para>
/// <para>Use the [JsonIgnore] attribute to ignore specific public properties or variables.</para>
/// <para>Object to be serialized must have a parameterless constructor.</para>
/// </summary>
public static class JsonSerialization
{
	/// <summary>
	/// Writes the given object instance to a Json file.
	/// <para>Object type must have a parameterless constructor.</para>
	/// <para>Only Public properties and variables will be written to the file. These can be any type though, even other classes.</para>
	/// <para>If there are public properties/variables that you do not want written to the file, decorate them with the [JsonIgnore] attribute.</para>
	/// </summary>
	/// <typeparam name="T">The type of object being written to the file.</typeparam>
	/// <param name="filePath">The file path to write the object instance to.</param>
	/// <param name="objectToWrite">The object instance to write to the file.</param>
	/// <param name="append">If false the file will be overwritten if it already exists. If true the contents will be appended to the file.</param>
	public static void WriteToJsonFile<T>(string filePath, T objectToWrite, bool append = false) where T : new()
	{
		TextWriter writer = null;
		try
		{
			var contentsToWriteToFile = Newtonsoft.Json.JsonConvert.SerializeObject(objectToWrite);
			writer = new StreamWriter(filePath, append);
			writer.Write(contentsToWriteToFile);
		}
		finally
		{
			if (writer != null)
				writer.Close();
		}
	}

	/// <summary>
	/// Reads an object instance from an Json file.
	/// <para>Object type must have a parameterless constructor.</para>
	/// </summary>
	/// <typeparam name="T">The type of object to read from the file.</typeparam>
	/// <param name="filePath">The file path to read the object instance from.</param>
	/// <returns>Returns a new instance of the object read from the Json file.</returns>
	public static T ReadFromJsonFile<T>(string filePath) where T : new()
	{
		TextReader reader = null;
		try
		{
			reader = new StreamReader(filePath);
			var fileContents = reader.ReadToEnd();
			return Newtonsoft.Json.JsonConvert.DeserializeObject<T>(fileContents);
		}
		finally
		{
			if (reader != null)
				reader.Close();
		}
	}
}

And here is an example of how to use it:

public class Person
{
	public string Name { get; set; }
	public int Age = 20;
	public Address HomeAddress { get; set;}
	private string _thisWillNotGetWrittenToTheFile = "because it is not public.";

	[JsonIgnore]
	public string ThisWillNotBeWrittenToTheFile = "because of the [JsonIgnore] attribute.";
}

public class Address
{
	public string StreetAddress { get; set; }
	public string City { get; set; }
}

// And then in some function.
Person person = new Person() { Name = "Dan", Age = 30; HomeAddress = new Address() { StreetAddress = "123 My St", City = "Regina" }};
List<Person> people = GetListOfPeople();
JsonSerialization.WriteToJsonFile<Person>("C:\person.txt", person);
JsonSerialization.WriteToJsonFile<List<People>>("C:\people.txt", people);

// Then in some other function.
Person person = JsonSerialization.ReadFromJsonFile<Person>("C:\person.txt");
List<Person> people = JsonSerialization.ReadFromJsonFile<List<Person>>("C:\people.txt");

 

As you can see, the Json example is almost identical to the Xml example, with the exception of using the [JsonIgnore] attribute instead of [XmlIgnore].

 

Writing and Reading an object to / from a Json file (using the JavaScriptSerializer in the System.Web.Extensions assembly)

There are many Json serialization libraries out there.  I mentioned the Newtonsoft.Json one because it is very popular, and I am also mentioning this JavaScriptSerializer one because it is built into the .Net framework.  The catch with this one though is that it requires the Full .Net 4.0 framework, not just the .Net Framework 4.0 Client Profile.

The caveats to be aware of are the same between the Newtonsoft.Json and JavaScriptSerializer libraries, except instead of using [JsonIgnore] you would use [ScriptIgnore].

Be aware that the JavaScriptSerializer is in the System.Web.Extensions assembly, but in the System.Web.Script.Serialization namespace.  Here is the code from the Newtonsoft.Json code snippet that needs to be replaced in order to use the JavaScriptSerializer:

// In WriteFromJsonFile<T>() function replace:
var contentsToWriteToFile = Newtonsoft.Json.JsonConvert.SerializeObject(objectToWrite);
// with:
var contentsToWriteToFile = new System.Web.Script.Serialization.JavaScriptSerializer().Serialize(objectToWrite);

// In ReadFromJsonFile<T>() function replace:
return Newtonsoft.Json.JsonConvert.DeserializeObject<T>(fileContents);
// with:
return new System.Web.Script.Serialization.JavaScriptSerializer().Deserialize<T>(fileContents);

 

Happy Coding!

Categories: C#, Json, XML Tags: , , , , , , , , , , ,