Friday, August 28, 2009

Reading from Isolated Storage

You learned how to write to Isolated Storage in the previous post. Here is how you read from it:
IsolatedStorageFile isf = IsolatedStorageFile.GetUserStoreForAssembly();
IsolatedStorageFileStream stream = new IsolatedStorageFileStream("pref.txt", FileMode.OpenOrCreate, isf);
StreamReader sr = new StreamReader(stream);
SetWidthAndHeight(sr.ReadToEnd());
sr.Close();

Isolated Storage

Isolated Storage - A private file system managed by the .NET Framework.

Why use it?
Isolated Storage provides a safe location, regardless of user access level, to store information that can be isolated by each user on the machine to store such things as user preferences.

How do I use it?
Here's an example where I store the height and width of a window in a text file on closing.
IsolatedStorageFile isf = IsolatedStorageFile.GetUserStoreForAssembly();
IsolatedStorageFileStream stream = new IsolatedStorageFileStream("pref.txt", FileMode.Create, isf);
StreamWriter sw = new StreamWriter(stream);
sw.WriteLine("Width:" + this.Width);
sw.WriteLine("Height:" + this.Height);
sw.Close();

Where did this text file actually get saved?
Well, on my machine (Vista) it went into C:\Users\[User]\AppData\Local\IsolatedStorage and then through 3 more cryptic folder names until it reached the AssemFiles folder where the "pref.txt" file showed up.

The location can change depending on the OS. See here for other OS locations.

More Info: Article: When and when not to use Isolated Storage

Thursday, August 27, 2009

Reading Data from the Configuration (*.config) File

In the previous post I saved data to the config file. Here is an example of how you would read data from the config file:
if (ConfigurationManager.AppSettings["Width"] != null)
{
    this.Width = Convert.ToDouble(ConfigurationManager.AppSettings["Width"]);
}

if (ConfigurationManager.AppSettings["Height"] != null)
{
    this.Height = Convert.ToDouble(ConfigurationManager.AppSettings["Height"]);
}


More Info: MSDN: ConfigurationManager.AppSettings

Saving Data to the Configuration (*.config) File

Sometimes you want to save data to the configuration file so it's available the next time the application runs. An example of this is saving a window's last height and width upon closing. Here's how you do it:
Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);

config.AppSettings.Settings.Remove("Width");
config.AppSettings.Settings.Add("Width", this.Width.ToString());
config.AppSettings.Settings.Remove("Height");
config.AppSettings.Settings.Add("Height", this.Height.ToString());

config.Save(ConfigurationSaveMode.Modified);

Mark, why do I have to call the Remove method before adding?

Every time you call the Add method it appends data instead of overwriting it. Without calling the Remove method your configuration file will go from this:
<configuration>
    <appSettings>
        <add key="Width" value="402" />
        <add key="Height" value="177" />
    </appSettings>
</configuration>
to this on the next save:
<configuration>
    <appSettings>
        <add key="Width" value="402,300" />
        <add key="Height" value="177,154" />
    </appSettings>
</configuration>
More Info: MSDN: Configuration Class

Friday, August 7, 2009

Starting another .NET program using the Application Domain Class

The AppDomain class allows you to start another .NET assembly in it's own memory space (separate from your main program's memory space). This is different from executing a process from a thread which is contained in your main program's memory space.
Here's an example of how you can do this with the .NET assembly's path:
AppDomain d = AppDomain.CreateDomain("New Domain");
d.ExecuteAssembly(@"C:\LoginToExternalApp.exe");
You can't call just any exe though. It has to run within the .NET Framework runtime. So something like this won't work:
AppDomain d = AppDomain.CreateDomain("New Domain");
d.ExecuteAssembly(@"C:\Program Files\Adobe\Reader 9.0\Reader\AcroRd32.exe");
Another way to start an app is to create a reference to it and then call ExecuteAssemblyByName.
AppDomain appDomain = AppDomain.CreateDomain("New Domain");
appDomain.ExecuteAssemblyByName("LoginToExternalApp");
The user may manually close the application you start up programmatically. If you need to close it down programmatically though, you can use AppDomain.Unload method:
AppDomain.Unload(d);


More Info: MSDN: AppDomain Class

Thursday, August 6, 2009

Getting data back from a thread

How do you get data back from a thread if you don't know when it'll finish running? You do it by using a delegate. When the code in the thread is complete, it'll call a delegate that is referenced back at the originating code.

Here are the basic steps:
  1. Define a delegate.
  2. public delegate void ReturnResult(object sender);
  3. In the class that contains the method to be called, create a field/property of your new delegate.
  4. public ReturnResult ReturnResultDelegate;
  5. In the class that starts the thread, define a method that matches your delegate's signature. This is the method that will be receiving the callback from the thread.
  6. static void CheckReturnedResult(object sender)
    {
        Math m = (Math) sender;
        Console.WriteLine("Result:" + m.Result);
    }
  7. Instantiate the class and set your delegate to the method you created.
  8. Math math = new Math();
    math.Value1 = 1;
    math.Value2 = 3;
    math.ReturnResultDelegate = CheckReturnedResult;
  9. Have your class call the delegate somewhere when the thread runs.
  10. public void Add(object o)
    {
        Result = Value1 + Value2;
        ReturnResultDelegate(this);
    }
Optionally, you can send just the return result back instead of the whole class.
Let's put everything together and see how it all works in this console application:
using System;
using System.Threading;

namespace ThreadingApp
{
    class Program
    {
        static void Main(string[] args)
        {
            Math math = new Math();
            math.Value1 = 1;
            math.Value2 = 3;
            math.ReturnResultDelegate = CheckReturnedResult;

            ThreadPool.QueueUserWorkItem(math.Add);
            Thread.Sleep(1000);
        }

        static void CheckReturnedResult(object sender)
        {
            Math m = (Math)sender;
            Console.WriteLine("Result:" + m.Result);
        }
    }

    public class Math
    {
        public int Value1;
        public int Value2;
        public int Result;
        public delegate void ReturnResult(object sender);

        public ReturnResult ReturnResultDelegate;

        public void Add(object o)
        {
            Result = Value1 + Value2;
            ReturnResultDelegate(this);
        }
    }
}
In this sample, I minimize the scope of the delegate by including it in the class being called (Math). You might declare it outside the class if needed.

Another way to pass data into a thread

In the previous thread post I showed a way you can pass information into a thread. Here is another way in which you instantiate the class containing the method you want your thread to call first.
Math math = new Math();
math.Value1 = 1;
math.Value2 = 3;

ThreadPool.QueueUserWorkItem(math.Add);
Now when math.Add is called, it has all the information it needs to do its work.

Easiest way to start a thread

The easiest way to start a process on another thread is to call ThreadPool.QueueUserWorkItem.
Example:
// DoSomeWork is a method.
ThreadPool.QueueUserWorkItem(DoSomeWork);
Note: The DoSomeWork method has to have an Object as a parameter.
private static void DoSomeWork(object info)
{
    // Code that does work.
}
If you need to pass data into your method, you can do so:
ThreadPool.QueueUserWorkItem(DoSomeWork, "Info")
You will just have to cast the object back into the appropriate data type.
private static void DoSomeWork(object info)
{
    string workInfo = (string) info;
    // Code that does work.
}


More Info: MSDN: QueueUserWorkItem

Wednesday, August 5, 2009

Adding Text to Images

Adding text to an image requires 2 steps:
  • Create a Graphics object
  • Create a Font object
  • Call Graphics.DrawString method
Code Example:
private void Form1_Paint(object sender, PaintEventArgs e)
{
    Graphics g = e.Graphics;
    Font f = new Font("Arial", 12, FontStyle.Bold);
    g.DrawString("Copyright 2009", f, Brushes.Black, 10, 10);
}
Why would you do this?
Most likely you wouldn't. You would use a label instead. But you can also draw strings on images and that is most likely what you would use this method for.

More Info: MSDN: Graphics.DrawString Method

Tuesday, August 4, 2009

Assigning Images to Windows Controls

Use a Bitmap or Image class.
button1.BackgroundImage = new Bitmap("bm.jpg");
// or
pictureBox1.BackgroundImage = Image.FromFile("bm.jpg");


More Info: MSDN: Image Class

Creating and Saving a Picture

There are 3 steps to creating and saving a picture:
  1. Create a Bitmap object.
  2. Edit it using Graphics object.
  3. Call Bitmap.Save method to save it.

Code Example:
Bitmap bm = new Bitmap(100, 100);
Graphics g = Graphics.FromImage(bm);
g.DrawEllipse(new Pen(Color.Red), new Rectangle(5, 5, 90, 90));
bm.Save("bm.jpg", ImageFormat.Jpeg);
Here is what it looks like:
If you would like to edit an existing image instead, you can use:
Bitmap bm = new Bitmap("existingImage.jpg");

More Info: MSDN: Bitmap Class