Upgrade of Application/User Settings between application versions

One of the polished areas of .Net Framework 2.0 is manipulation with Application/User settings in WinForms applications. You can store almost anything in appropriate app.config or user.config file, and to manipulate with those settings with ease – wrapper class created around Settings allows you to use settings as just another field in your code.

But, what about of upgrade existing settings once when you create new version of the application?

That is supported via Upgrade() method:


	 Settings.Default.Upgrade();
	 Settings.Default.Save();

You can add one user bool setting (for example UpgradeSettings) with default value True, which will be a flag if upgrade is carried out; code can be:


	if (Settings.Default.UpgradeSettings)
	{
		 Settings.Default.Upgrade();
		 Settings.Default.UpgradeSettings = false;
		 Settings.Default.Save();
	}

Suppose that your application is simple little application, not a Click-Once or even distributed with installer – just archive which users can download, unpack and start using.

If you new version picks user settings folder from previous one, all is fine – Upgrade() method will find previous version and upgrade them:

Application versions - as expected

Suppose that this is true:

  • both versions are either signed or not (if you do signing of assembly between versions, Upgrade will fail)
  • both versions are either Click-Once applications or not (for the same reasons)

In theory, all is fine and Upgrade should do the job. But, what if all above is fulfilled and that you still have different user settings folder:

Application versions - mismatch

What can be reason that same application creates completely different user settings folder? This situation happened to me. After couple of hours, I could not determine reason, so I pulled out heavy tools – Reflector.

If you start tracking .Upgrade(), it goes to: ApplicationSettingsBase.Upgrade which simply calls IApplicationSettingsProvider.Upgrade for each provider present. For simple WinForms application, appropriate provider is LocalFileSettingsProvider. After some more thorough analysis, internal method for determining folder of settings store is:

System.Configuration.ClientConfigPaths

One of elements in determining local store for user settings was hashed value derived from Evidences for given assembly.

It turns out that one of Evidence elements for non-signed assemblies is path from where assembly/application is launched!.

To summarize: in order for Upgrade() of settings succeed, your new version of application should be deployed in the exact same folder where previous version is found; otherwise, Upgrade() will fail (or at least, it won’t do what you expected 🙂 )

How to render Html content into JPG/JPEG image in best quality (WinForms)

How to save Html to JPG/JPEG Image and how to save it in best quality?

On recent project, I had a tasks to:

  1. Save result of Html page to JPG image
  2. To do that in best possible quality

Both tasks sounds trivial, but I did not find quite satisfying solutions, so, I came up with one bellow.

First, you need WebBrowser control – it is basicaly wrapper around ActiveX Internet Explorer interface.

Once when Html content is ready (local or remote), ask control to call your procedure upon completion:

webBrowser1.DocumentCompleted += new WebBrowserDocumentCompletedEventHandler(webBrowser1_DocumentCompleted);

and start render your content:

webBrowser1.Navigate( UriToRender )

In DocumentCompletedEventHandler, call your routine for rendering content to image:

void webBrowser1_DocumentCompleted(object sender, 
WebBrowserDocumentCompletedEventArgs e)
{
  if( GetImageFromHtml(currentFile, webBrowser1) )
  {
     toolStripStatusLabel1.Text = "Image saved.";
  }
  else
  {
     toolStripStatusLabel1.Text = "Sorry, there was problem with saving.";
  }
}

And here is code for rendering Html to image:

/// <summary>
/// Method for saving Html content into image
/// </summary>
/// <param name="imageName">File name of image</param>
/// <param name="webBrowser">Target WebBrowser control to query for content</param>
/// <returns>True if save was succesfull</returns>
/// <seealso cref="System.Windows.Forms.WebBrowser" />

public bool GetImageFromHtml(string imageName, WebBrowser webBrowser)
{
  if (webBrowser.Document == null)
  {
     return false;
  }

  Cursor current = Cursor;
  Cursor = Cursors.WaitCursor;

  // Give time to WebBrowser control to finish rendering of document
  Thread.Sleep(200);

  try
  {

     // save old width / height
     Size originalSize = new Size(webBrowser1.Width, webBrowser1.Height);
     
     // Change to full scroll size
     int scrollHeight = webBrowser.Document.Body.ScrollRectangle.Height;
     int scrollWidth = webBrowser.Document.Body.ScrollRectangle.Width;
     
     Bitmap image = new Bitmap(scrollWidth, scrollHeight);
     webBrowser.Size = new Size(scrollWidth, scrollHeight);
     
     // Draw to image
     webBrowser.DrawToBitmap(image, webBrowser.ClientRectangle);
     webBrowser.Size = originalSize;

     // Old one with bad quality:
     // image.Save(imageName, ImageFormat.Jpeg);

     // Save in full quality
     SaveJPG(image, imageName, 100);

     return true;
  }

  catch { return false; }

  finally {  Cursor = current; }
}

Usual Image.Save method saves image in number of formats; however, quality is some default (bad) quality; and I needed more control over it. So, here are methods which allow that kind of control:

/// <summary>
/// Gets codec info by given mimeType
/// </summary>
/// <param name="mimeType">mimeType to lookup for</param>
/// <returns>ImageCodecInfo if all ok or null</returns>

public static ImageCodecInfo GetCodecInfo(String mimeType)
{
  ImageCodecInfo[] encoders;
  encoders = ImageCodecInfo.GetImageEncoders();
  for (int iterator = 0; iterator < encoders.Length; ++iterator)
  {
     if (encoders[iterator].MimeType == mimeType)
        return encoders[iterator];
  }
  return null;
}

/// <summary>
/// Save an Image to JPEG with given compression quality
/// </summary>
/// <param name="image">Image to save</param>
/// <param name="imageName">File name to store image</param>
/// <param name="quality">Quality parameter: 0 - lowest quality, smallest size, 
/// 100 - max quality and size</param>
/// <returns>True if save was succesfull</returns>

public static bool SaveJPG(Image image, string imageName, long qual)
{
  EncoderParameters eps = new EncoderParameters(1);
  eps.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, qual);
  ImageCodecInfo ici = GetCodecInfo("image/jpeg");

  if(ici == null) { return false; }

  image.Save(imageName, ici, eps);
  return true;
} 

Now you can select jpeg image quality and render html the way you like.

Digg!

How to make Windows Form app truly Full Screen (and to hide Taskbar) in C#?

One of sound-like-simple questions is “how to make your application truly Full Screen” i.e. not showing Taskbar or anything like that.

Initial approach is obvious:


    targetForm.WindowState = FormWindowState.Maximized;
    targetForm.FormBorderStyle = FormBorderStyle.None;
    targetForm.TopMost = true;

Does it work? Well, sort of. If your Taskbar have default setting unchecked for “Keep the taskbar on top of other windows”, this will present your application in all it’s glory all over screen estate.

However, if the Taskbar is set to appear on top of all others, this won’t help – your application won’t cover it.

Let’s go further – next step is to use P/Invoke and to engage Win32 API services. There is easy way to hide particular window. So, find the Taskbar and hide it:


    private const int SW_HIDE = 0;
    private const int SW_SHOW = 1;

    [DllImport("user32.dll")]
    private static extern int FindWindow(string className, string windowText);
    [DllImport("user32.dll")]
    private static extern int ShowWindow(int hwnd, int command);

    int hWnd = FindWindow("Shell_TrayWnd", "");
    ShowWindow(hWnd, SW_HIDE);

    targetForm.WindowState = FormWindowState.Maximized;
    targetForm.FormBorderStyle = FormBorderStyle.None;
    targetForm.TopMost = true;

(you need to add using System.Runtime.InteropServices;)

Is this better? In theory yes – Taskbar is hidden, but your application still does not occupy whole screen – place where Taskbar was is not used.

Real and proven solution is to make request to WinAPI that your form take whole screen estate – Taskbar will hide itself in that case. Full information about that can be found in KB Article Q179363: How To Cover the Task Bar with a Window and here is the code:


/// <summary>
/// Selected Win AI Function Calls
/// </summary>

public class WinApi
{
    [DllImport("user32.dll", EntryPoint = "GetSystemMetrics")]
    public static extern int GetSystemMetrics(int which);

    [DllImport("user32.dll")]
    public static extern void 
        SetWindowPos(IntPtr hwnd, IntPtr hwndInsertAfter,
                     int X, int Y, int width, int height, uint flags);        
    
    private const int SM_CXSCREEN = 0;
    private const int SM_CYSCREEN = 1;
    private static IntPtr HWND_TOP = IntPtr.Zero;
    private const int SWP_SHOWWINDOW = 64; // 0x0040
    
    public static int ScreenX
    {
        get { return GetSystemMetrics(SM_CXSCREEN);}
    }
    
    public static int ScreenY
    {
        get { return GetSystemMetrics(SM_CYSCREEN);}
    }
    
    public static void SetWinFullScreen(IntPtr hwnd)
    {
        SetWindowPos(hwnd, HWND_TOP, 0, 0, ScreenX, ScreenY, SWP_SHOWWINDOW);
    }
}

/// <summary>
/// Class used to preserve / restore state of the form
/// </summary>
public class FormState
{
    private FormWindowState winState;
    private FormBorderStyle brdStyle;
    private bool topMost;
    private Rectangle bounds;

    private bool IsMaximized = false;

    public void Maximize(Form targetForm)
    {
        if (!IsMaximized)
        {
            IsMaximized = true;
            Save(targetForm);
            targetForm.WindowState = FormWindowState.Maximized;
            targetForm.FormBorderStyle = FormBorderStyle.None;
            targetForm.TopMost = true;
            WinApi.SetWinFullScreen(targetForm.Handle);
        }
    }
    
    public void Save(Form targetForm)
    {
        winState = targetForm.WindowState;
        brdStyle = targetForm.FormBorderStyle;
        topMost = targetForm.TopMost;
        bounds = targetForm.Bounds;
    }

    public void Restore(Form targetForm)
    {
        targetForm.WindowState = winState;
        targetForm.FormBorderStyle = brdStyle;
        targetForm.TopMost = topMost;
        targetForm.Bounds = bounds;
        IsMaximized = false;
    }
}

Code for example application is here: MaxWinForm.zip

Validation of input elements and Form Closing

One of very often questions ask by WinForms developers is “How to make sure that if Form is Closing, no validation occurs in contained controls”?

For reference, validating input is rather straightforward:

1. Add ErrorProvider to Form
2. For control in question, add handlers for Validating and Validated events:


        private void tbFixPath_Validating(object sender, CancelEventArgs e)
        {
            if (!ControlValidated(tbFixPath.Text))
            {
                // Cancel the event and select the text to be corrected by the user.
                e.Cancel = true;
                tbFixPath.Select(0, tbFixPath.Text.Length);

                // Set the ErrorProvider error with the text to display. 
                this.errorProvider1.SetError(tbFixPath, 
                   "Sorry, something went wrong during validation. Please check.");
            }
        }

        private void tbFixPath_Validated(object sender, EventArgs e)
        {
                // After successful validation, clear error message.
                errorProvider1.SetError(tbFixPath, "");
        }

3. Everything is fine, until user tries to close form where some input elements are not validated – closing will fail. In order to prevent this, if you are absolutely sure that you want to close such form, just add:


        private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
        {
                e.Cancel = false;
        }

so that no cancellation of closing event will occur.

I know that this is basic 🙂 but after I was ask for 10th time in 6 months, I figured that is better to note this somewhere 🙂