Quantcast
Channel: cyotek.com Blog Summary Feed
Viewing all 559 articles
Browse latest View live

Converting 2D arrays to 1D and accessing as either 2D or 1D

$
0
0

While working on a recent gaming project, I was originally using 2D arrays to store information relating to the different levels in the game. But when it came to loop through the contents of these levels, it wasn't as straightforward to do a simple foreach loop due to the multiple dimensions.

Instead, I changed the code so that the 2D data was stored in a single dimension array. By using row-major order you can calculate any position in 2D space and map it into the 1D array. This then allows you to continue accessing the data using 2D co-ordinates, but opens up 1D access too.

Defining your array

Given the size of your 2D array, the 1D creation code is trivial:

T[] items = new T[width * height];

Converting 2D co-ordinates into 1D index

Once your have your array, converting a 2D co-ordinate such as 3, 4 into the correct index of your 1D array using row-major order using the following formula:

y * width + x

Converting 1D index into 2D co-ordinates

The calculation to convert a 1D index into a 2D co-ordinate is fairly straightforward:

y = index / width;
x = index % width;

Putting it together - the ArrayMap<T> class

To avoid constantly having to repeat the calculations, I created a generic ArrayMap class that I could use to store any data type in a 1D array, and access the values using either indexes or co-ordinates, as well as adding enumerable support. The class is very straightforward to use:

ArrayMap&lt;int> grid;
Size size;int value;

size = new Size(10, 10);
value = 0;
grid = new ArrayMap&lt;int>(size);// set values via 2D co-ordinatesfor (int y = 0; y &lt; size.Height; y++)
{for (int x = 0; x &lt; size.Width; x++)
  {
    grid[x, y] = value;
    value++;
  }
}// get values via 2D co-ordinates
Console.WriteLine(grid[9, 0]); // 9
Console.WriteLine(grid[0, 9]); // 90
Console.WriteLine(grid[9, 9]); // 99// set values via indexesfor (int i = 0; i &lt; grid.Count; i++)
  grid[i] = i;// get values via index
Console.WriteLine(grid[9]); // 9
Console.WriteLine(grid[90]); // 90
Console.WriteLine(grid[99]); // 99// enumerate itemsforeach (int i in grid)
  Console.WriteLine(i);// get index
Console.WriteLine(grid.GetItemIndex(9, 9)); // 99// get location
Console.WriteLine(grid.GetItemLocation(99)); // 9,9

Below is the full source to the class.

using System;using System.Collections;using System.Collections.Generic;namespace BinaryRealms.Engine
{publicclass ArrayMap&lt;T> : IEnumerable&lt;T>
  {private T[] _items;private Size _size;public ArrayMap()
    { }public ArrayMap(int width, int height)
      : this(new Size(width, height))
    { }public ArrayMap(Size size)
      : this()
    {this.Size = size;
    }public IEnumerator&lt;T> GetEnumerator()
    {foreach (T item in _items)yieldreturn item;
    }publicint GetItemIndex(int x, int y)
    {if (x &lt; 0 || x >= this.Size.Width)thrownew IndexOutOfRangeException("X is out of range");elseif (y &lt; 0 || y >= this.Size.Height)thrownew IndexOutOfRangeException("Y is out of range");return y * this.Size.Width + x;
    }publicint GetItemIndex(Point point)
    {returnthis.GetItemIndex(point.X, point.Y);
    }public Point GetItemLocation(int index)
    {
      Point point;if (index &lt; 0 || index >= _items.Length)thrownew IndexOutOfRangeException("Index is out of range");

      point = new Point();
      point.Y = index / this.Size.Width;
      point.X = index % this.Size.Width;return point;
    }publicint Count
    { get { return _items.Length; } }public Size Size
    {get { return _size; }set
      {
        _size = value;
        _items = new T[_size.Width * _size.Height];
      }
    }public T this[Point location]
    {get { returnthis[location.X, location.Y]; }set { this[location.X, location.Y] = value; }
    }public T this[int x, int y]
    {get { returnthis[this.GetItemIndex(x, y)]; }set { this[this.GetItemIndex(x, y)] = value; }
    }public T this[int index]
    {get { return _items[index]; }set { _items[index] = value; }
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
      returnthis.GetEnumerator();
    }
  }
}

Currently I'm using this class without any problems, but if you spot any errors or think it could do with anything else, please let me know!

All content Copyright © by Cyotek Ltd or its respective writers. Permission to reproduce news and web log entries and other RSS feed content in unmodified form without notice is granted provided they are not used to endorse or promote any products or opinions (other than what was expressed by the author) and without taking them out of context. Written permission from the copyright owner must be obtained for everything else.
Original URL of this content is http://www.cyotek.com/blog/converting-2d-arrays-to-1d-and-accessing-as-either-2d-or-1d?source=rss


Creating an image viewer in C# Part 5: Selecting part of an image

$
0
0
A newer version of this code is now available.

Part 4 of this series (by far the most popular article on cyotek.com) was supposed to be the end, but recently I was asked if was possible to select part of an image for saving it to a file. After implementing the new functionality and lacking ideas for a new post on other matters, here we are with a new part!

The demonstration program showing the selection functionality

Getting Started

If you aren't already familiar with the ImageBox component, you may wish to view parts 1, 2, 3 and 4 for the original background and specification of the control.

First thing is to add some new properties, along with backing events. These are:

  • SelectionMode - Determines if selection is available within the control
  • SelectionColor - Primary color for drawing the selection region
  • SelectionRegion - The currently selected region.
  • LimitSelectionToImage - This property allows you to control if the selection region can be drawn outside the image boundaries.
  • IsSelecting - This property returns if a selection operation is in progress

If the SelectionMode property is set, then the AutoPan and AllowClickZoom properties will both be set to false to avoid conflicting actions.

We also need a couple of new events not directly tried to properties.

  • Selecting - Occurs when the user starts to draw a selection region and can be used to cancel the action.
  • Selected - Occurs when the user completes drawing a selection region

These events are called when setting the IsSelecting property:

[Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]publicvirtualbool IsSelecting
{get { return _isSelecting; }protectedset
  {if (_isSelecting != value)
    {
      CancelEventArgs args;

      args = new CancelEventArgs();if (value)this.OnSelecting(args);elsethis.OnSelected(EventArgs.Empty);if (!args.Cancel)
        _isSelecting = value;
    }
  }
}

Drawing the selection highlight

Before adding support for defining the selection region, we'll add the code to draw it - that way we'll know the code to define the region works! To do this, we'll modify the existing OnPaint override, and insert a call to a new method named DrawSelection:

protectedoverridevoid OnPaint(PaintEventArgs e)
{/* Snipped existing code for brevity */// draw the selectionif (this.SelectionRegion != Rectangle.Empty)this.DrawSelection(e);base.OnPaint(e);
}

The DrawSelection method itself is very straightforward. First it fills the region with a translucent variant of the SelectionColor property, then draws a solid outline around this. A clip region is also applied to avoid overwriting the controls borders.

As with most of the methods and properties in the ImageBox control, it has been marked as virtual to allow you to override it and provide your own drawing implementation if required, without needing to redraw all of the control.

protectedvirtualvoid DrawSelection(PaintEventArgs e)
{
  RectangleF rect;

  e.Graphics.SetClip(this.GetInsideViewPort(true));

  rect = this.GetOffsetRectangle(this.SelectionRegion);using (Brush brush = new SolidBrush(Color.FromArgb(128, this.SelectionColor)))
    e.Graphics.FillRectangle(brush, rect);using (Pen pen = new Pen(this.SelectionColor))
    e.Graphics.DrawRectangle(pen, rect.X, rect.Y, rect.Width, rect.Height);

  e.Graphics.ResetClip();
}

The GetOffsetRectangle method will be described a little further down this article.

Defining the selection region

Currently the selection region can only be defined via the mouse; there is no keyboard support. To do this, we'll do the usual overriding of MouseDown, MouseMove and MouseUp

protectedoverridevoid OnMouseDown(MouseEventArgs e)
{base.OnMouseDown(e);/* Snipped existing code for brevity */if (e.Button == MouseButtons.Left && this.SelectionMode != ImageBoxSelectionMode.None)this.SelectionRegion = Rectangle.Empty;
}protectedoverridevoid OnMouseMove(MouseEventArgs e)
{base.OnMouseMove(e);if (e.Button == MouseButtons.Left)
  {/* Snipped existing code for brevity */this.ProcessSelection(e);
  }
}protectedoverridevoid OnMouseUp(MouseEventArgs e)
{base.OnMouseUp(e);if (this.IsPanning)this.IsPanning = false;if (this.IsSelecting)this.IsSelecting = false;
}

OnMouseDown and OnMouseUp aren't being used for much in this case, the former is used to clear an existing selection region, the later to notify that the selection is no longer being defined. OnMouseMove calls the ProcessSelection method which is where all the action happens.

protectedvirtualvoid ProcessSelection(MouseEventArgs e)
{if (this.SelectionMode != ImageBoxSelectionMode.None)
  {if (!this.IsSelecting)
    {
      _startMousePosition = e.Location;this.IsSelecting = true;
    }

First, we check to make sure a valid selection mode is set. Then, if a selection operation hasn't been initiated, we attempt to set the IsSelecting property. As noted above, this property will call the Selecting event allowing the selection to be cancelled if required by the implementing application.

if (this.IsSelecting)
    {float x;float y;float w;float h;
      Point imageOffset;

      imageOffset = this.GetImageViewPort().Location;if (e.X < _startMousePosition.X)
      {
        x = e.X;
        w = _startMousePosition.X - e.X;
      }else
      {
        x = _startMousePosition.X;
        w = e.X - _startMousePosition.X;
      }if (e.Y < _startMousePosition.Y)
      {
        y = e.Y;
        h = _startMousePosition.Y - e.Y;
      }else
      {
        y = _startMousePosition.Y;
        h = e.Y - _startMousePosition.Y;
      }

      x = x - imageOffset.X - this.AutoScrollPosition.X;
      y = y - imageOffset.Y - this.AutoScrollPosition.Y;

If selection was allowed, we construct the co-ordinates for a rectangle, automatically switching values around to ensure that the rectangle will always have a positive width and height. We'll also offset the co-ordinates if the image has been scrolled or if it has been centred (or both!).

      x = x / (float)this.ZoomFactor;
      y = y / (float)this.ZoomFactor;
      w = w / (float)this.ZoomFactor;
      h = h / (float)this.ZoomFactor;

As this is the zoomable scrolling image control, we also need to rescale the rectangle according to the current zoom level. This ensures the SelectionRegion property always returns a rectangle that describes the selection at 100% zoom.

if (this.LimitSelectionToImage)
      {if (x < 0)
          x = 0;if (y < 0)
          y = 0;if (x + w > this.Image.Width)
          w = this.Image.Width - x;if (y + h > this.Image.Height)
          h = this.Image.Height - y;
      }this.SelectionRegion = new RectangleF(x, y, w, h);
    }
  }
}

The final step is to constrain the rectangle to the image size if the LimitSelectionToImage property is set, before assigning the final rectangle to the SelectionRegion property.

And that's pretty much all there is to it.

Scaling and offsetting

When using the control in our own products, it's very rarely to display a single image, but rather to display multiple items, be it sprites in a sprite sheet or tiles in a map. These implementations therefore often require the ability to get a single item, for example to display hover effects. This can be tricky with a control that scrolls, zooms and centres the image. Rather than repeat ZoomFactor calculations (and worse AutoScrollPosition) everywhere, we added a number of helper methods named GetOffset* and GetScaled*. Calling these with a "normal" value, will return that value repositioned and rescaled according to the current state of the control. An example of this is the DrawSelection method described above which needs ensure the current selection region is rendered correctly.

publicvirtual RectangleF GetScaledRectangle(RectangleF source)
{returnnew RectangleF
    (
      (float)(source.Left * this.ZoomFactor),
      (float)(source.Top * this.ZoomFactor),
      (float)(source.Width * this.ZoomFactor),
      (float)(source.Height * this.ZoomFactor)
    );
}publicvirtual RectangleF GetOffsetRectangle(RectangleF source)
{
  RectangleF viewport;
  RectangleF scaled;float offsetX;float offsetY;

  viewport = this.GetImageViewPort();
  scaled = this.GetScaledRectangle(source);
  offsetX = viewport.Left + this.Padding.Left + this.AutoScrollPosition.X;
  offsetY = viewport.Top + this.Padding.Top + this.AutoScrollPosition.Y;returnnew RectangleF(new PointF(scaled.Left + offsetX, scaled.Top + offsetY), scaled.Size);
}

Versions of these methods exist for the following structures:

  • Point
  • PointF
  • Size
  • SizeF
  • Rectangle
  • RectangleF

These methods can come in extremely useful depending on how you are using the control!

Cropping an image

The demonstration program displays two ImageBox controls, the first allows you to select part of an image, and the second displays the cropped selection. I didn't add any sort of crop functionality to the control itself, but the following snippets shows how the demonstration program creates the cropped version.

Rectangle rect;if (_previewImage != null)
  _previewImage.Dispose();

rect = new Rectangle((int)imageBox.SelectionRegion.X, (int)imageBox.SelectionRegion.Y, (int)imageBox.SelectionRegion.Width, (int)imageBox.SelectionRegion.Height);

_previewImage = new Bitmap(rect.Width, rect.Height);using (Graphics g = Graphics.FromImage(_previewImage))
  g.DrawImage(imageBox.Image, new Rectangle(Point.Empty, rect.Size), rect, GraphicsUnit.Pixel);
}

previewImageBox.Image = _previewImage;

Finishing touches

We'll finish off by adding a couple of helper methods that implementers can call:

publicvirtualvoid SelectAll()
{if (this.Image == null)thrownew InvalidOperationException("No image set");this.SelectionRegion = new RectangleF(PointF.Empty, this.Image.Size);
}publicvirtualvoid SelectNone()
{this.SelectionRegion = RectangleF.Empty;
}

Known issues

Currently, if you try and draw the selection bigger than the visible area of the control, it will work, but it will not scroll the control for you. I also was going to add the ability to move or modify the selection but ran out of time for this particular post.

As always, if you have any comments or questions, please contact us!

Downloads

All content Copyright © by Cyotek Ltd or its respective writers. Permission to reproduce news and web log entries and other RSS feed content in unmodified form without notice is granted provided they are not used to endorse or promote any products or opinions (other than what was expressed by the author) and without taking them out of context. Written permission from the copyright owner must be obtained for everything else.
Original URL of this content is http://www.cyotek.com/blog/creating-an-image-viewer-in-csharp-part-5-selecting-part-of-an-image?source=rss

Arcade explosion generator

$
0
0

Over the past few weeks I've been messing around creating a unique graphics for our Jewel Rush game. One of the things I was experimenting with was explosion animations. Although tools exist for generating explosions the problem with most of these is that they create large sprites which don't shrink well, and the output is a bit more realistic than what I was looking for.

And while I'm competent enough to do application graphics (more or less!), gaming graphics are a completely different kettle of fish!

A screenshot from Missile Command

Above is a screenshot from Missile Command, a classic from Atari. That's the sort of explosions I wanted to create, so I wrote a small tool that would create these sort of graphics in a random (but reproducible) fashion and export them to images for use in other tools such as Spriter. As it turned out, the graphics it produces didn't end up quite that way (I was having problems with the intersection stuff) but it's usable enough for the purposes I want.

A sample of the default output

Another sample using some custom settings.

The application was thrown together over the weekend so it's probably not hugely robust and may contain a small army of bugs. But it works and is possibly an interesting starting point for other projects. There's some interesting bits of code here and there, although I'm not writing about the implementation of the code.

Application Features

  • Configuration settings can be saved and reloaded for tweaking of favoured settings. Uses basic Reflection serialization as XmlSerializer can't handle colors without having to create duplicate color properties in string format.
  • Can export either a complete sprite sheet, or the individual images
  • Copy the sprite sheet to the clipboard (although I noticed that transparent doesn't work if you do, something to look at later)
  • Uses the ImageBox (of course!) for displaying previews
  • The TrackBar control embedded in the ToolStrip is a custom component inheriting from ToolStripControlHost which can be reused. And once you understand the principles, it's so easy to host other controls.

Graphic Settings

  • Either specify a seed to always recreate the same explosion, or use a random seed each time. (If you find a seed you like, clicking the seed number in the status bar will apply it to your configuration settings).
  • Specify the number of animation frames that will be generated, and the size of the frames
  • Specify the maximum number of explosion booms available at once. There's also an option to automatically remove and recreate "expired" blooms.
  • Choose the colors used to render the bloms
  • Specify the percentage by which blooms grow (and shrink), and how many growth states there are. Once a bloom has shrunk to its minimum size, it is expired and no longer draw.
  • Anti alias options, useful if you don't want pixel graphics
  • Border size and growth
  • Set a random order, in which newly created blooms will be inserted randomly in the list.
  • An experimental mask mode which was supposed to enable me to create those Missile Command style XOR drawing. However, it doesn't really work and I'll probably have another go at it at some point.

An example generated by the projectAnother example of generated output

Room for improvement

Everything can be improved, one of the ideas I'd had for this tool was greater control over blooms, allowing you configure their locations etc with better precision but it wasn't necessary for the graphic I was creating. As mentioned above, the masking doesn't work as expected, it would have been nice if it did. Some better rendering would be a plus too, at the moment the "explosions" are simple rings of color. Some noise or other minor particle effects to make them a little less uniform would probably look interesting.

Source Code

Source code, and optionally pre-compiled binaries, are available from the link below. The code has been compiled against the .NET 3.5 Client Profile. Due to some minor use of Linq and auto generated properties a small amount of work would be needed to compile against .NET 2.0. I'm afraid comments are somewhat lacking as well, I wasn't planning on releasing this publicly originally.

If anyone creates any interesting graphics or improves upon the code, we'd love to hear from you.

Downloads

All content Copyright © by Cyotek Ltd or its respective writers. Permission to reproduce news and web log entries and other RSS feed content in unmodified form without notice is granted provided they are not used to endorse or promote any products or opinions (other than what was expressed by the author) and without taking them out of context. Written permission from the copyright owner must be obtained for everything else.
Original URL of this content is http://www.cyotek.com/blog/arcade-explosion-generator?source=rss

Displaying the contents of a PDF file in an ASP.NET application using GhostScript

$
0
0

After receiving quite a few requests on making the PDF image conversion work in a web application, I wanted to see how hard it would be to do. Not hard at all as it turns out, I had a nice working sample running with a bare 5 minutes of work.

The sample available for download below is a basic ASP.NET application, comprised of a single page with an IHttpHandler for displaying the image. In order to make this sample as easy as possible, it uses pure server side controls and code, nothing client side.

Getting Started

In order to run this sample, you'll need the Cyotek.GhostScript and Cyotek.GhostScript.PdfConversion.zip components described in a previous article.

You'll also need to download GhostScript. As with my other articles on the subject, please make sure you check their license terms - they seem very keen that people don't use the GPL version or distribute GhostScript without a commercial license.

Locating gsdll32.dll

In order for this to work, gsdll32.dll needs to be somewhere in your applications path. This could be in your system32 directory on 32bit Windows, or SysWOW64 on 64bit Windows.

While developing this sample, I also tried having the file in the bin directory of the website - this also worked fine. However, as the website was running on my local machine, it's probably running in Full Trust, and I have no idea if it will work in Medium Trust or lower.

I'm running 64bit Windows!

Congratulations! I have nothing but issues with 32bit web servers. But I digress. The sample projects I have provided on this website all use the 32bit version of GhostScript. There is a 64bit version available, but I haven't downloaded it to test. Your options should be as follows:

  • Build against the 64bit GhostScript DLL. This may need some refactoring if their public API has changed. At the very least, you'll need to change the DLL filename in the native method calls.
  • Using IIS7 or higher? Keep using the 32bit version, and set your worker pool to run in 32bit mode
  • Using IIS6? Commiserations, I feel your pain. The only option here, if you stay 32bit, is to have the entire IIS run as 32bit.

I have tested on a Windows 7 Professional 64bit machine as follows:

  • Firstly, using IISExpress which is running as a 32bit process
  • Secondly, using IIS7 with a custom application pool running in 32bit mode

Both of these scenarios worked perfectly well.

Creating the solution

Create a new ASP.NET Web Forms Site

Note: Even though this example uses pure WebForms, there's no reason that this sort of code won't work fine in ASP.NET MVC or any other .NET framework of your choice.

Open up Default.aspx and add some controls similar to the following:

&lt;%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="GhostScriptWebTest._Default" %>&lt;!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">&lt;html xmlns="http://www.w3.org/1999/xhtml">&lt;head runat="server">&lt;title>PDF Conversion Example&lt;/title>&lt;/head>&lt;body>&lt;form id="form1" runat="server">&lt;div>&lt;p>&lt;asp:LinkButton runat="server" ID="previousLinkButton" Text="Previous" OnClick="previousLinkButton_Click" />&lt;asp:LinkButton runat="server" ID="nextLinkButton" Text="Next" OnClick="nextLinkButton_Click" />&lt;/p>&lt;p>&lt;asp:Image runat="server" ID="pdfImage" ImageUrl="~/PdfImage.ashx?fileName=sample.pdf&page=1" />&lt;/p>&lt;/div>&lt;/form>&lt;/body>&lt;/html>

The controls should be fairly self explanatory! The main thing of interest is the pdfImage Image control - this will call a Generic Handler that I'll describe in the next section. Note that VS2010 and VS2012 have another option, an ASP.NET Handler - this implements the same IHttpHandler interface but doesn't have a .ashx file and is registered differently. If you are using IIS7 or above, you're probably better off using that.

Note that by default the pdfImage control is pointing to a sample file named sample.pdf - add any old PDF to the root of your website and name it sample. Ensure that the Build Action for the PDF is set to Content, otherwise it won't be deployed with your application.

Creating the image handler

Tutorials on creating image handlers with IHttpHandler can be found scattered throughout the net, so I'll not go into how they work, but just describe the implementation I'm using in this example. Add a new generic handler to your project, then fill in the ProcessRequest method as follows. Make sure you add the two GhostScript API components to your solution and add references to them to your web application first!

using System;using System.Drawing;using System.Drawing.Imaging;using System.Web;using Cyotek.GhostScript.PdfConversion;namespace GhostScriptWebTest
{publicclass PdfImage : IHttpHandler
  {publicvoid ProcessRequest(HttpContext context)
    {string fileName;int pageNumber;
      Pdf2Image convertor;
      Bitmap image;

      fileName = context.Server.MapPath("~/" + context.Request.QueryString["fileName"]);
      pageNumber = Convert.ToInt32(context.Request.QueryString["page"]);// convert the image
      convertor = new Pdf2Image(fileName);
      image = convertor.GetImage(pageNumber);// set the content type
      context.Response.ContentType = "image/png";// save the image directly to the response stream
      image.Save(context.Response.OutputStream, ImageFormat.Png);
    }publicbool IsReusable
    { get { returntrue; } }
  }
}

Again, this is extremely simple code. I extract the query string of the request to obtain the file name of the PDF document to convert, and the page to display. I then create an instance of the Pdf2Image class, and grab an image of the specified page.

Next, you need to set the ContentType of the Response object so the web browser knows what to do with your content. Finally, I save the image directly to the response's OutputStream. Make sure that the format you save the image as matches the content type you've specified.

With these steps complete, building and running the website should present you with a pair of hyper links, and the first page of your PDF file as a static image. [Well, it will if you add a pair of blank event handlers for those defined for the two hyperlink buttons anyway]

Simple navigation

Now that we can display our PDF, we'll add some basic navigation. Open up the code behind file for Default.aspx and fill in the event handlers for the two hyperlink buttons.

using System;using System.Collections.Specialized;using System.Web;using Cyotek.GhostScript.PdfConversion;namespace GhostScriptWebTest
{publicpartialclass _Default : System.Web.UI.Page
  {protectedvoid previousLinkButton_Click(object sender, EventArgs e)
    {this.IncrementPage(-1);
    }protectedvoid nextLinkButton_Click(object sender, EventArgs e)
    {this.IncrementPage(1);
    }privatevoid IncrementPage(int increment)
    {
      NameValueCollection queryString;int pageNumber;string pdfFileName;
      Pdf2Image converter;

      queryString = HttpUtility.ParseQueryString(pdfImage.ImageUrl.Substring(pdfImage.ImageUrl.IndexOf("?")));
      pdfFileName = queryString["fileName"];
      pageNumber = Convert.ToInt32(queryString["page"]) + increment;
      converter = new Pdf2Image(this.Server.MapPath("~/" + pdfFileName));if (pageNumber > 0 && pageNumber <= converter.PageCount)
        pdfImage.ImageUrl = string.Format("~/PdfImage.ashx?fileName={0}&page={1}", pdfFileName, pageNumber);
    }
  }
}

As with the image handler, this code simply extracts the file name of the PDF file and the current page number. It also creates a new instance of the Pdf2Image class in order to obtain the number of pages in the document. If the new page number is in range, it updates the ImageUrl of the pdfImage causing the image handler to pull back the next page.

In Conclusion

This sample is pretty inefficient and at the very least should be caching the images. But, it's as simple an example as I can make. Hopefully someone will find it useful. At the present time I'm not working with the GhostScript API library so I suspect this will be the last article on the subject for the time being.

Downloads

All content Copyright © by Cyotek Ltd or its respective writers. Permission to reproduce news and web log entries and other RSS feed content in unmodified form without notice is granted provided they are not used to endorse or promote any products or opinions (other than what was expressed by the author) and without taking them out of context. Written permission from the copyright owner must be obtained for everything else.
Original URL of this content is http://www.cyotek.com/blog/displaying-the-contents-of-a-pdf-file-in-an-asp-net-application-using-ghostscript?source=rss

Creating a multi-paged container control with design time support

$
0
0

This article describes adding design time support for a TabControl-like component which renders the same way the Project Properties in Visual Studio 2012.

The TabList demonstration application.

This is the first time I've tried to make more advanced use of component designers so there are going to be areas that I'm not aware of or have not implemented correctly. The component seems to be working fine, but it's entirely possible that bugs exist, which could cause problems. Caveat emptor!

Overview of the control

For this article, I'm not going to delve into how the control itself was put together as I want to focus on the design time support, so I'm just going to provide a quick overview.

  • TabList - the main control
  • TabListPage - these are hosted by the TabList to provided multi-paged support
  • TabListControlCollection - a custom ControlCollection that handles TabListPages, and prevents adding other controls directly onto the TabList
  • TabListPageCollection - a strongly typed wrapper for TabListPage objects

The basics of these four classes are all based on the TabControl. If you know how to use that, then you know how to use the TabList control, some property names have changed but otherwise it's pretty similar.

For rendering support, we use these classes:

  • ITabListPageRenderer - interface to be implemented by rendering classes
  • TabListPageRenderer - base class to inherit for render support, and also provides a default renderer property
  • TabListPageState - flags which describe the state of a TabListPage
  • DefaultTabListPageRenderer - simple renderer which draws a header in a Visual Studio 2012-esque style.

And finally, we have the two designers which this article will concentrate on:

  • TabListDesigner - designer class for the TabList control
  • TabListPageDesigner - designer class for the TabListPage control

Implementing the TabListDesigner

As the TabList control is a container control, we can't use the base ControlDesigner. Instead, we'll use ParentControlDesigner which has a bunch of extra functionality we need.

Initializing a new control

Normally, I initialize a component via the constructor of the control. This is fine when you're initializing properties to default values, but what about adding child items? Consider for example the TabControl. When add one of these to a form, it generates two hosted pages. If you remove these, they don't come back. If you've ever looked at the designer generated code for a control, you'll see it will add items to a collection, but doesn't clear the collection first so creating items via the initialization method of a component would be problematic.

Fortunately for us, the designer has two methods you can override. InitializeNewComponent is called when you create a new instance of the designed type. InitializeExistingComponent can be used to modify an existing component. There's also a third override, InitializeNonDefault although I'm not sure when this is called.

For our purposes, overriding the InitializeNewComponent method is enough:

publicoverridevoid InitializeNewComponent(IDictionary defaultValues)
{base.InitializeNewComponent(defaultValues);// add two default pages to each new control and reset the selected indexthis.AddTabListPage();this.AddTabListPage();this.TabListControl.SelectedIndex = 0;
}

Now, whenever you add a TabList control onto a designer surface such as a Form, it'll get two shiny new TabListPages.

Hooking up events

For our designer, we need to know when certain actions occur so we can act accordingly - for example, to disable the Remove verb if there's nothing to remove. We'll set these up by overriding the Initialize method.

publicoverridevoid Initialize(IComponent component)
{
  TabList control;
  ISelectionService selectionService;
  IComponentChangeService changeService;base.Initialize(component);// attach an event so we can be notified when the selected components in the host change
  selectionService = (ISelectionService)this.GetService(typeof(ISelectionService));if (selectionService != null)
    selectionService.SelectionChanged += this.OnSelectionChanged;// attach an event to notify us of when a component has been modified
  changeService = (IComponentChangeService)this.GetService(typeof(IComponentChangeService));if (changeService != null)
    changeService.ComponentChanged += this.OnComponentChanged;// attach an event so we can tell when the SelectedIndex of the TabList control changes
  control = component as TabList;if (control != null)
    control.SelectedIndexChanged += this.OnSelectedIndexChanged;
}

OnSelectionChanged

The first event we attached as ISelectionService.SelectionChanged. This event is raised when the selected components change. We'll use this event to automatically activate a given TabListPage if a control hosted upon it is selected.

privatevoid OnSelectionChanged(object sender, EventArgs e)
{
  ISelectionService service;

  service = (ISelectionService)this.GetService(typeof(ISelectionService));if (service != null)
  {
    TabList control;

    control = this.TabListControl;foreach (object component in service.GetSelectedComponents())
    {
      TabListPage ownedPage;// check to see if one of the selected controls is hosted on a TabListPage. If it is, // activate the page. This means, if for example, you select a control via the// IDE's properties window, the relavent TabListPage will be activated

      ownedPage = this.GetComponentOwner(component);if (ownedPage != null&& ownedPage.Parent == control)
      {
        control.SelectedPage = ownedPage;break;
      }
    }
  }
}

OnComponentChanged

The second event IComponentChangeService.ComponentChanged is raised when the RaiseComponentChanged method is called. We'll describe how this method works a bit further on, but for now, we use the event to determine if there are any tab pages in the control - if there are, the remove command is enabled, otherwise it's disabled. (We'll also describe the verbs further down too!)

privatevoid OnComponentChanged(object sender, ComponentChangedEventArgs e)
{// disable the Remove command if we dont' have anything we can actually removeif (_removeVerb != null)
    _removeVerb.Enabled = this.TabListControl.TabListPageCount > 0;
}

OnSelectedIndexChanged

The final event, TabList.SelectedIndexChanged is on the TabList control itself. We use this event to select the TabList component for designing due to how component selection seems to work when you mix runtime and design time functionality.

privatevoid OnSelectedIndexChanged(object sender, EventArgs e)
{
  ISelectionService service;

  service = (ISelectionService)this.GetService(typeof(ISelectionService));if (service != null)
  {// set the TabList control as the selected object. We need to do this as if the control is selected as a result// of GetHitTest returning true, normal designer actions don't seem to take place// Alternatively, we could select the selected TabListPage instead but might as well stick with the standard behaviour
    service.SetSelectedComponents(newobject[] { this.Control });
  }
}

Verbs

I mentioned verbs above, but just what are they? Well, they are commands you attach to the context and tasks menu of controls. To do this, override the Verbs property of your designer and create a verbs collection.

publicoverride DesignerVerbCollection Verbs
{get
  {if (_verbs == null)
    {
      _verbs = new DesignerVerbCollection();

      _addVerb = new DesignerVerb("Add TabListPage", this.AddVerbHandler) { Description = "Add a new TabListPage to the parent control." };
      _removeVerb = new DesignerVerb("Remove TabListPage", this.RemoveVerbHandler) { Description = "Remove the currently selected TabListPage from the parent control." };

      _verbs.Add(_addVerb);
      _verbs.Add(_removeVerb);
    }

    return _verbs;
  }
}

Each verb binds to an event handler. For our purposes the events are simple and just pass through into other methods.

privatevoid AddVerbHandler(object sender, EventArgs e)
{this.AddTabListPage();
}privatevoid RemoveVerbHandler(object sender, EventArgs e)
{this.RemoveSelectedTabListPage();
}

I suppose you could just use an anonymous delegate instead.

Modifying a component with undo support

If you are making multiple changes a control, and one of these goes wrong, the IDE won't automatically undo the changes for you and you will need to handle this yourself. Fortunately, the IDE does provide the facility via designer transactions. In additional to providing a single undo for a number of operations, using transactions can also be good for performance as UI updates are delayed until the transaction is complete.

The code below is called by the Add verb and adds a new TabListPage to the control.

These are the basic steps for making changes:

  • Create a transaction via IDesignerHost.CreateTransaction
  • Notify the designer of impending changes via the RaiseComponentChanging method
  • Make the change
  • Notify the designer that the change has been made via the RaiseComponentChanged method. This will raise the IComponentChangeService.ComponentChanged event mentioned above.
  • Either Commit or Cancel the transaction

In this case, despite wrapping the transaction in a using statement, I've got got an explicit trycatch block to cancel the transaction in the event of an error. I'm not sure if this is strictly necessary however.

protectedvirtualvoid AddTabListPage()
{
  TabList control;
  IDesignerHost host;

  control = this.TabListControl;
  host = (IDesignerHost)this.GetService(typeof(IDesignerHost));if (host != null)
  {using (DesignerTransaction transaction = host.CreateTransaction(string.Format("Add TabListPage to '{0}'", control.Name)))
    {try
      {
        TabListPage page;
        MemberDescriptor controlsProperty;

        page = (TabListPage)host.CreateComponent(typeof(TabListPage));
        controlsProperty = TypeDescriptor.GetProperties(control)["Controls"];// tell the designer we're about to start making changesthis.RaiseComponentChanging(controlsProperty);// set the text to match the name
        page.Text = page.Name;// add the new control to the parent, and set it to be the active page
        control.Controls.Add(page);
        control.SelectedIndex = control.TabListPageCount - 1;// inform the designer we're finished making changesthis.RaiseComponentChanged(controlsProperty, null, null);// commit the transaction
        transaction.Commit();
      }catch
      {
        transaction.Cancel();throw;
      }
    }
  }
}

The handler for the remove verb does pretty much the same thing, except we use IDesignerHost.DestroyComponent to remove the selected TabListPage control.

protectedvirtualvoid RemoveSelectedTabListPage()
{
  TabList control;

  control = this.TabListControl;if (control != null&& control.TabListPageCount != 0)
  {
    IDesignerHost host;

    host = (IDesignerHost)this.GetService(typeof(IDesignerHost));if (host != null)
    {using (DesignerTransaction transaction = host.CreateTransaction(string.Format("Remove TabListPage from '{0}'", control.Name)))
      {try
        {
          MemberDescriptor controlsProperty;

          controlsProperty = TypeDescriptor.GetProperties(control)["Controls"];// inform the designer we're about to make changesthis.RaiseComponentChanging(controlsProperty);// remove the tab page
          host.DestroyComponent(control.SelectedPage);// tell the designer w're finished making changesthis.RaiseComponentChanged(controlsProperty, null, null);// commit the transaction
          transaction.Commit();
        }catch
        {
          transaction.Cancel();throw;
        }
      }
    }
  }
}

Adding controls to the selected TabListPage

If the TabList control is selected and you try to drag a control on it, you'll get an error stating that only TabListPage controls can be hosted. By overriding the CreateToolCore method, we can intercept the control creation, and forward it onto the current TabListPage via the InvokeCreateTool method.

protectedoverride IComponent[] CreateToolCore(ToolboxItem tool, int x, int y, int width, int height, bool hasLocation, bool hasSize)
{
  TabList control;
  IDesignerHost host;

  control = this.TabListControl;// prevent controls from being created directly on the TabListif (control.SelectedPage == null)thrownew ArgumentException(string.Format("Cannot add control '{0}', no page is selected.", tool.DisplayName));

  host = (IDesignerHost)this.GetService(typeof(IDesignerHost));if (host != null)
  {
    ParentControlDesigner childDesigner;

    childDesigner = (ParentControlDesigner)host.GetDesigner(control.SelectedPage);

    // add controls onto the TabListPage control instead of the TabList
    ParentControlDesigner.InvokeCreateTool(childDesigner, tool);
  }returnnull;
}

Returning null via CreateToolCore prevents the control from being created on the TabList. The reminder of the logic forwards the call onto the selected TabListPage, if one is available.

Allowing TabListPage selection at design-time

As you'll have noticed, most controls can't be used at design time - when you click a control it just selects it. This default behaviour is a serious problem for our component as if you can't active other pages, how can you add controls to them? Fortunately, this is extremely easy to implement as the designer provides a GetHitTest method which you can override. If you return true from this method, then mouse clicks will be processed by the underlying control instead of the designer.

protectedoverridebool GetHitTest(Point point)
{
  TabList control;bool result;
  Point location;// return true if the mouse is located over a TabListPage header// this allows you to switch pages at design time with the mouse// rather than just selecting the control as it would otherwise

  control = this.TabListControl;
  location = control.PointToClient(point);
  result = control.HitTest(location) != null;return result;
}

In the above code, we translate the provided mouse co-ordinates into the client co-ordinates, then test to see if they are on the header of a TabListPage. If they are, we return true and the call will then be forward onto the TabList control which will then selected that page.

There is one side effect of this behaviour. As we have essentially intercepted the mouse call, that means the TabList control isn't selected. This behaviour is inconsistent with standard behaviour and this is why when the designer was initialized we hooked into the SelectedIndexChanged event of the TabList control. With this hooked, as soon as the SelectedIndex property is changed we can manually select the TabList control. Of course, if you'd rather, you could change that code to select the active TabListPage instead, but again that's inconsistent with standard behaviour.

Unfortunately there's also another side effect I discovered - the context menu no longer works if you right click on an area where you allow mouse clicks to pass through. Again, this is fairly straightforward to work around by overriding WndProc and intercepting the WM_CONTEXTMENU message.

protectedoverridevoid WndProc(ref Message m)
{switch (m.Msg)
  {case 0x7b: // WM_CONTEXTMENU
      Point position;// For some reason the context menu is no longer displayed when right clicking the control// By hooking into the WM_CONTEXTMENU context message we can display the menu ourselves

      position = Cursor.Position;

      this.OnContextMenu(position.X, position.Y);break;default:base.WndProc(ref m);break;
  }
}

Note: Normally I wouldn't use "magic numbers" as I have here. But at the same time, I don't want to define WM_CONTEXTMENU in this class - for my internal projects, I link to an assembly I've created which contains all the Win32 API functionality that I use. Linking that to this not possible for this example and I don't want to create a Native class for a just a single member. So this time I'll cheat and leave an inline magic number.

The final side effect I've found is double clicking to open the default event handler doesn't work either.

Design time control paining

The final section of the TabListDesigner class I want to discuss is design time painting. Normally, in the OnPaint overriding of my control, I would have a block similar to the below.

protectedoverridevoid OnPaint(PaintEventArgs e)
{base.OnPaint(e);if (this.DesignMode)
  {// Design time painting here
  }
}

While there's nothing wrong with this approach, if you are using a designer than you have another option, which saves you having to do design time checks each time your contain is painted at runtime. The designer has an OnPaintAdornments method, just override this to perform your design time drawing.

protectedoverridevoid OnPaintAdornments(PaintEventArgs pe)
{base.OnPaintAdornments(pe);// outline the control at design time as we don't have any borders
  ControlPaint.DrawFocusRectangle(pe.Graphics, this.Control.ClientRectangle);
}

As the TabList doesn't have a border property, I draw a dotted line around the control using ControlPaint.DrawFocusRectangle.

Implementing the TabListPage designer

Although the TabListPage control is basically a Panel control with a bunch of properties and events hidden, it still needs a designer to override some functionality. For the TabListPageDesigner class, we'll inherit from ScrollableControlDesigner.

Removing sizing and moving handles

As the TabList control takes care of sizing its child TabListPage controls, we don't really want the user to be able to resize or move them at design time. By overriding the SelectionRules property, you can define exactly which handles are displayed. As I don't want the control to be moved or sized, I get rid of everything via the Locked flag.

publicoverride SelectionRules SelectionRules
{ get { return SelectionRules.Locked; } }

Preventing the component from being re-parented

The CanBeParentedTo method is used to determine if a component can be hosted by another control. I'm overriding this to make sure that they can only be parented on another TabList control. Although, as I've disabled the dragging of TabListPage controls with selection rules above, you can't drag them to reparent anyway.

publicoverridebool CanBeParentedTo(IDesigner parentDesigner)
{return parentDesigner != null&& parentDesigner.Component is TabList;
}

Known Issues

  • As described above, if you double click one of the TabListPage headers nothing happens. Normally, you'd expect a code window to be opened at the default event handler for the control. While it should be possible to trap the WM_LBUTTONDBLCLK message, I don't know how to open a code window, or create a default event handler is one is missing.
  • Another issue I spotted is that I can't Cut (or Copy) a TagListPage from one TabList control to another. Not sure why yet, but I'll update the source on GitHub when I fix it.

The source

Get the source code from the link below. I've also uploaded it to GitHub, feel free to fork and make pull requests to make this component even better!

Downloads

All content Copyright © by Cyotek Ltd or its respective writers. Permission to reproduce news and web log entries and other RSS feed content in unmodified form without notice is granted provided they are not used to endorse or promote any products or opinions (other than what was expressed by the author) and without taking them out of context. Written permission from the copyright owner must be obtained for everything else.
Original URL of this content is http://www.cyotek.com/blog/creating-a-multi-paged-container-control-with-design-time-support?source=rss

Zooming into a fixed point on a ScrollableControl

$
0
0

If I'd built subtitle support into the CMS that powers this website, then surely the subtitle would have been "or how I fixed that annoying zoom bug in the ImageBox control". And with that digression out of the way, onto the article, a nice and short one for a change!

I should probably point out that this article doesn't describe how to actually do any zooming (as that is dependant on what it is you are actually doing a zoom upon), but rather how to keep the viewport focused on a given point after zooming. To learn about zooming, please see previous articles that describe the ImageBox control in detail.

Users of the ImageBox control are probably aware of the zoom bug, where each time you use the mouse wheel to zoom in or out, the final image position is slightly offset, as shown by this short animation:

Mouse wheel zoom - subtly broken.

Fixing this bug is actually quite simple and I'm actually embarrassed at how long it took to fix and how I missed the solution for so long. The key to resolving this issue is finding out the document position under the mouse (by which I mean the position in the entire scroll area, not just the visible viewport) before applying the zoom, and then recalculating this position with the new zoom level, offset by the mouse position in the client control.

As it's probably easier just to show you the code rather than try and describe it, it results in this small function:

publicvirtualvoid ScrollTo(Point imageLocation, Point relativeDisplayPoint)
{int x;int y;

  x = (int)(imageLocation.X * this.ZoomFactor) - relativeDisplayPoint.X;
  y = (int)(imageLocation.Y * this.ZoomFactor) - relativeDisplayPoint.Y;this.AutoScrollPosition = new Point(x, y);
}

To use it, you add code similar to the following where you process mouse clicks, or mouse wheel, however you control zooming with the mouse:

Point cursorPosition;
Point currentPixel;int currentZoom;// TODO: Obtain cursor position from MouseEventArgs etc.

currentPixel = this.PointToImage(cursorPosition);
currentZoom = this.Zoom;// TODO: Perform zoom hereif (this.Zoom != currentZoom)this.ScrollTo(currentPixel, cursorPosition);

So how does this work?

  1. Get the mouse cursor position, relative to the control
  2. Convert that position into the position of your virtual document - for the ImageBox control we use the PointToImage method
  3. Perform your zoom and recalculate the document scroll size etc.
  4. Call the ScrollTo method, passing in the document position and mouse cursor position

And now you end up with something similar to this:

Mouse wheel zoom - works a lot smoother now.

There is one case where this does not work as expected - when you scroll in or out sufficiently to remove the scrollbars, or when moving from no-scrollbars to scrollbars. However, I think is fine given it works so well the rest of the time!

That's fine, but where's the ImageBox update?

Thanks to a generous donation from a visitor to the site, I recently sat down to work on the ImageBox control and resolve some of the issues - like the scrolling above. The next update has quite a lot of new functionality (better keyboard support, configurable zoom levels, flicker free scrolling and a handful of bug fixes to name a few of the changes) and will be posted presently. While the build is being finalized however, the above code will work fine in current builds of the ImageBox, if you adjust for the pixel offset the current PointToImage implementation uses.

All content Copyright © by Cyotek Ltd or its respective writers. Permission to reproduce news and web log entries and other RSS feed content in unmodified form without notice is granted provided they are not used to endorse or promote any products or opinions (other than what was expressed by the author) and without taking them out of context. Written permission from the copyright owner must be obtained for everything else.
Original URL of this content is http://www.cyotek.com/blog/zooming-into-a-fixed-point-on-a-scrollablecontrol?source=rss

Zooming to fit a region in a ScrollableControl

$
0
0

I suspect the titles of these last pair of articles are a touch misleading as they talk about extended zoom operations without actually describing a zoom process (as this is already part of other ImageBox articles. Unfortunately I can't really think of better titles and the theory is generic enough to be applied to any type of zooming, not just the ImageBox.

My previous article touched on zooming in a ScrollableControl while keeping the content correctly aligned to a fixed point, usually the mouse position prior to the zoom. This article expands on that with another new feature in the upcoming ImageBox update, zooming to a given region. You've probably seen this behaviour in other paint programs, where you select a Zoom tool, draw a rectangle, and the document is automatically zoomed to fit.

Again, this is actually quite simple to achieve and this blog post is pretty much just a reference to me of how I did this so I don't forget next time I want something similar! We'll start off by quickly describing a method that I forgot to include in the previous post - CenterAt. This simple method centers the view port on the given document location. This use useful for when you want to use ScrollTo without providing a relative offset... or for centering the display on a selection rectangle.

publicvirtualvoid CenterAt(Point imageLocation)
{this.ScrollTo(imageLocation, new Point(this.ClientSize.Width / 2, this.ClientSize.Height / 2));
}

Very straightforward, it simply calls ScrollTo, using the center of the control as the offset. Now for the actual ZoomToRegion method:

publicvirtualvoid ZoomToRegion(RectangleF rectangle)
{double ratioX;double ratioY;int cx;int cy;

  ratioX = this.ClientSize.Width / rectangle.Width;
  ratioY = this.ClientSize.Height / rectangle.Height;
  cx = (int)(rectangle.X + (rectangle.Width / 2));
  cy = (int)(rectangle.Y + (rectangle.Height / 2));this.Zoom = (int)(Math.Min(ratioX, ratioY) * 100);this.CenterAt(new Point(cx, cy));
}

This accepts a RectangleF structure (you could use a Rectangle, but then if you attempt to draw selection regions on a zoomed out document, rounding from float to int would render your selections useless), and it then calculates a new zoom factor and offset.

  1. First, the ration of the width and height of the region against the width and height of the view port is calculated
  2. We use the smallest ratio (to ensure if that everything you selected appears when the zoom is applied) to calculate the new zom level
  3. After this, we define the center of the rectangle
  4. With all the calculations done, we set the zoom level of the control
  5. And finally, we call our new CenterAt method to center the view port on the center of the source region

In the actual ImageBox control, a new SelectionMode has been added - Zoom. As the name somewhat logically suggests, when this mode is active, after the user draws a selection rectangle, the control then zooms to match the rectangle they have drawn. This updated mode is called from the OnSelected method, as follows:

if (this.SelectionRegion != null&& this.SelectionRegion.Width > ImageBox.SelectionDeadZone && this.SelectionRegion.Height > ImageBox.SelectionDeadZone)
{this.ZoomToRegion(this.SelectionRegion);this.SelectionRegion = RectangleF.Empty;
}

The mysterious ImageBox.SelectionDeadZone field is a constant currently set to 5, and basically ensures the user selects a valid rectangle first - when I was testing the first iteration of this code, having the mouse wobble as you clicked the control was enough to generate a 1x1 rectangle, definitely not a good user experience!

The only disadvantage is this functionality only lends itself to zooming it, and not out.

All content Copyright © by Cyotek Ltd or its respective writers. Permission to reproduce news and web log entries and other RSS feed content in unmodified form without notice is granted provided they are not used to endorse or promote any products or opinions (other than what was expressed by the author) and without taking them out of context. Written permission from the copyright owner must be obtained for everything else.
Original URL of this content is http://www.cyotek.com/blog/zooming-to-fit-a-region-in-a-scrollablecontrol?source=rss

ImageBox update, version 1.1.0.0

$
0
0
A newer version of this code is now available.

The ImageBox control has had quite a big update, you can download the source from the link below, or from our GitHub page.

Listed below are the changes made during this update, we hope you enjoy them!

The updated ImageBox demonstration application.

Changes and new features

  • Zooming with the mouse is now smoother, and the control attempts to keep the area under the mouse before the zoom in the same area after the zoon.
  • Added a ZoomLevels property which allows you to configure the different zoom levels supported by the control. Now instead of the control trying to guess the next zoom level, it cycles appropriately through the defined levels. Currently ZoomLevels (apart from the default series) can only be set at runtime.
  • The ZoomIncrement property has been removed due to the introduction of the new zoom levels.
  • New CenterAt and ScrollTo methods allow you to scroll to a given location in the source image.
  • Split shortcut handling into two methods ProcessScrollingShortcuts for handling arrow keys and ProcessImageShortcuts for handling pretty much anything else.
  • Added EnableShortcuts property, allowing the built in keyboard support to be disabled. When this property is true, ProcessImageShortcuts is not called, allowing the control to still be scrolled via the keyboard, but not zoomed etc.
  • Zooming can now be performed by the -/+ keys (OemMinus and Oemplus).
  • When zooming (except via mouse action), if the AutoCenter property is set, the control will always center the image even when scrollbars are present.
  • Nestable BeginUpdate and EndUpdate methods allow you to disable and enable painting of the control, for example when changing multiple properties at once.
  • Added a new GetSelectedImage method which creates a new Bitmap based on the current selection.
  • Added new FitRectangle method which takes a given rectangle and ensure it fits within the image boundaries
  • The AllowClickZoom property now defaults to false.
  • The PointToImage function no longer adds +1 to the result of the function.
  • Added a new ZoomToRegion method. This will caculate and appropriate zoom level and scrollbar positions to fit a given rectangle.
  • Added new SelectionMode.Zoom. When this mode is selected, drawing a region will automatically zoom and position the control to fit the region, after which the region is automatically cleared.

Bug fixes

  • Panning no longer tries to activate if no scrollbars are visible
  • A new base class, VirtualScrollableControl is now used instead of ScrollableControl. This removes completely the flicker issues present in previous versions of the control.
  • The BorderStyle property has been moved to the ScrollControl class, so that borders now correctly surround the control (including scrollbars) rather than just the client area.
  • If the AllowZoomClick property is true, the control no longer magically zooms after panning or selecting a region. Code previously in the OnMouseClick override is now in OnMouseUp.
  • If both AutoPan and a valid SelectionMode are set, only selections are processed, instead of the control tying to do both. As a result of this fix, setting the SelectionMode property no longer resets AutoPan
  • With the introduction of the VirtualScrollableControl, the MouseWheel event is now raised as expected.

Known issues

  • The ScrollProperties class hasn't been fully integrated with the ScrollControl, setting properties on this class won't update the owner control.

Downloads

All content Copyright © by Cyotek Ltd or its respective writers. Permission to reproduce news and web log entries and other RSS feed content in unmodified form without notice is granted provided they are not used to endorse or promote any products or opinions (other than what was expressed by the author) and without taking them out of context. Written permission from the copyright owner must be obtained for everything else.
Original URL of this content is http://www.cyotek.com/blog/imagebox-update-version-1-1-0-0?source=rss


Extracting email addresses from Outlook

$
0
0

The cyotek.com receives an awful lot of spam and a lot of this is sent to email addresses that don't exist. However, as we currently have catch all's enabled, it means we receive it regardless. This is compounded by the fact that I tend to create a unique email address for each website or service I interact with. And it's impossible to remember them all!

As a first step to deleting the catch alls, I wanted to see how many unique @cyotek.com addresses were in use. The simplest way of picking up these would be scanning PST files - we have email going back to 2002 in these files, and there's the odd backup elsewhere going back even further. Last time I used OLE Automation with Outlook was back in the days of VB6 and I recall well getting plagued with permission dialogs each time I dreamed of trying to access the API. Still, I thought I'd take a look.

A console application merrily extracting my personal email addresses from my Outlook store.

Setting up

Note: I tested this project on an Outlook profile which has loaded a primary PST, an archive PST, and a Gmail account. I haven't tested this with any other type of account (for example Exchange) or with accounts using non-SMTP email addresses. Caveat emptor!

The first thing to do is add a reference to the Outlook COM objects. I have VS2010 and VS2012 installed on this machine, and one of them has installed a bunch of prepared Office Interop DLL's into the GAC. Handy, I won't have to create my own! Adding a reference to the Microsoft Outlook 14.0 Object Library added three references, Microsoft.Office.Interop.Outlook.dll, Office.dll and stdole to my project.

Note: Depending on your version of VS / .NET Framework, the references may have a property named Embed Interop Types which defaults to true. When left at this, you may have problems debugging as you won't be able to access the objects properly through the Immediate window, instead getting an error similar to

"Member 'To' on embedded interop type 'Microsoft.Office.Interop.Outlook.MailItem' cannot be evaluated while debugging since it is never referenced in the program. Consider casting the source object to type 'dynamic' first or building with the 'Embed Interop Types' property set to false when debugging"

Probably a good idea to set this to false before debugging your code!

Connecting to Outlook

All the code below assumes that you have a using Microsoft.Office.Interop.Outlook; statement at the top of your code file.

Connecting to Outlook is easy enough, just create a new instance of the Application interface. We'll use as a root for everything else.

Application application;

application = new Application();

Remember I mentioned permission dialogs? Older versions of Outlook used to prompt for permissions. Outlook 2010 just seems to quietly get on with things. The only thing I've noticed is that if you try and create a new Application when Outlook isn't currently running, it will be silently started and the system tray icon will have a slightly different icon and a tooltip informing that some other program is using Outlook. Much nicer than previous behaviours!

Getting Account Folders

The Session property of the Application interface returns a NameSpace that details your Outlook setup, and allows access to accounts, profile details etc. However, for this project, the only thing I care about is the Folders property which returns a collection of MAPIFolder objects. In my case, it was the three top level folders for my profile - I was somewhat surprised that the Gmail account was loaded actually.

Now that we have a folder, we can scan it by enumerating the Items property. As Outlook folders can contain items of various types, you need to check the item type - I'm looking for MailItem objects in order to extract those addresses.

Pulling out email addresses

Each MailItem has Sender, To and Recipients properties. To seems to be just a string version of Recipients and so shall be completely ignored - why bother parsing it manually when Recipients already does it for you. The Sender property returns an AddressEntry, and each item in the Recipients collection (a Recipient) offers an AddressEntry property. So we're all set!

The following code snippet is from the example project, and basically shows how I scan a source MAPIFolder looking for MailItem objects.

protectedvirtualvoid ScanFolder(MAPIFolder folder)
{this.CurrentFolderIndex++;this.OnFolderScanning(new MAPIFolderEventArgs(folder, this.FolderCount, this.CurrentFolderIndex));// itemsforeach (object item in folder.Items)
  {if (item is MailItem)
    {
      MailItem email;

      email = (MailItem)item;

      // add the sender of the emailif (this.Options.HasFlag(Options.Sender))this.ProcessAddress(email.Sender);// add the recipies of the emailif (this.Options.HasFlag(Options.Recipient))
      {foreach (Recipient recipient in email.Recipients)this.ProcessAddress(recipient.AddressEntry);
      }
    }
  }// sub foldersif (this.Options.HasFlag(Options.SubFolders))
  {foreach (MAPIFolder childFolder in folder.Folders)this.ScanFolder(childFolder);
  }
}

When I find an AddressEntry to process, I call the following functions:

protectedvirtualvoid ProcessAddress(AddressEntry addressEntry)
{if (addressEntry != null&& (addressEntry.AddressEntryUserType == OlAddressEntryUserType.olSmtpAddressEntry || addressEntry.AddressEntryUserType == OlAddressEntryUserType.olOutlookContactAddressEntry))this.ProcessAddress(addressEntry.Address);elseif (addressEntry != null)
    Debug.Print("Unknown address type: {0} ({1})", addressEntry.AddressEntryUserType, addressEntry.Address);
}protectedvirtualvoid ProcessAddress(string emailAddress)
{int domainStartPosition;

  domainStartPosition = emailAddress.IndexOf("@");if (!string.IsNullOrEmpty(emailAddress) && domainStartPosition != -1)
  {bool canAdd;if (this.Options.HasFlag(Options.FilterByDomain))
      canAdd = this.IncludedDomains.Contains(emailAddress.Substring(domainStartPosition + 1));else
      canAdd = true;if (canAdd)this.EmailAddresses.Add(emailAddress);
  }
}

Although I'm scanning my entire PST, I don't want every single email address in there - I ran it once and it brought back just over 5000 addresses. What I want, is addresses tied to the domains I own, so I added some filtering for this. With this filtering enabled it returned a more managable 497 unique addresses. Although I'm not creating 497 aliases on the email server!

Wrapping up

This is a lot easier than what I was expecting, and in fact this is probably the smoothest piece of COM interop I've done with .NET yet. No strange errors, no forced to compile in 32bit mode, It Just Works.

You can find the example project in the link below.

Downloads

All content Copyright © by Cyotek Ltd or its respective writers. Permission to reproduce news and web log entries and other RSS feed content in unmodified form without notice is granted provided they are not used to endorse or promote any products or opinions (other than what was expressed by the author) and without taking them out of context. Written permission from the copyright owner must be obtained for everything else.
Original URL of this content is http://www.cyotek.com/blog/extracting-email-addresses-from-outlook?source=rss

Assembly Reference Scanner Sample

$
0
0

Due to an issue with our setup programs, certain files weren't getting updated if the version numbers hadn't changed. This led to crashes if you upgraded from one version to another. I wrote this tool as part of my diagnostic into why the programs were crashing complaining of wrong version numbers. It uses a reflection load only context to scan all assemblies in a folder, and checks each assemblies references against each other to ensure the versions match.

Note: This tool doesn't support assemblies in the GAC, only references to assemblies deployed in a folder. It is also a tool wrote in a hurry to solve a particular problem and may not fit your needs without modifications.

This is an example of expected behaviour:

A scan of Sitemap Creator showing all references correctly

And here's an example caused by upgrading one version of Sitemap Creator with another with a faulty setup causing some files not to be updated:

When you run cyosmc.exe with these version mismatches, things are going to go wrong!

Full C# source for the tool is available from the download link above.

Downloads

All content Copyright © by Cyotek Ltd or its respective writers. Permission to reproduce news and web log entries and other RSS feed content in unmodified form without notice is granted provided they are not used to endorse or promote any products or opinions (other than what was expressed by the author) and without taking them out of context. Written permission from the copyright owner must be obtained for everything else.
Original URL of this content is http://www.cyotek.com/blog/assembly-reference-scanner-sample?source=rss

Manually writing the byte order mark (BOM) for an encoding into a stream

$
0
0

I recently discovered a problem with our WebCopy and Cyotek Sitemap Creator products to do with "corruption" of plain text documents, where non-ANSI characters appeared incorrectly. It didn't take long to realize that these programs were saving text content as ANSI files. Which I found curious as Crawler library they use detects response encoding and uses this to save the files.

Or does it? Consider the code below:

string fileName;byte[] data;
Encoding encoding;

fileName = Path.GetTempFileName();
data = newbyte[0]; // assume you have a populated byte array!
encoding = Encoding.UTF8;using (FileStream stream = new FileStream(fileName, FileMode.Create))
{using (BinaryWriter writer = new BinaryWriter(stream, encoding))
    writer.Write(data);
}

Looking at this, you might be tempted to assume (as I did) that this code would save the content in the given encoding. When I tried opening one of the files generated by similar code to the above in Notepad++, I found they were encoded as ANSI files. Switching the encoding to UTF-8 immediately displayed the files correctly without the "corruption". So it seems the byte order mark (BOM) isn't actually written by the BinaryWriter - I think it only uses the given encoding for converting strings to a byte array. All this time I assumed files were being saved as UTF-8 (or whatever the response encoding was) and properly supported Unicode, and all this time I was wrong.

So how do you manually write a BOM into a document? The oddly named GetPreamble function available from the Encoding class is what you need - this returns the bytes that comprise the BOM, and you can then write this directly to your stream:

string fileName;byte[] data;
Encoding encoding;

fileName = Path.GetTempFileName();
data = newbyte[0]; // assume you have a populated byte array!
encoding = Encoding.UTF8;using (FileStream stream = new FileStream(fileName, FileMode.Create))
{using (BinaryWriter writer = new BinaryWriter(stream, encoding))
  {
    writer.Write(encoding.GetPreamble());
    writer.Write(data);
  }
}

Note that you only need to write a BOM if your document is actually supposed to be a text file - if it is "normal" binary data (such as an image or a gzip stream) then you definitely do not want to write a BOM, or you truly will have a corrupt file.

Now the files produced by WebCopy and Sitemap Creator are encoded correctly and I can be happily with yet another bug squashed, unhappy at yet another reminder of why I need to write a proper set of automated tests for the libraries I use, but happy again that I had another (albeit brief) tip to post on this blog.

All content Copyright © by Cyotek Ltd or its respective writers. Permission to reproduce news and web log entries and other RSS feed content in unmodified form without notice is granted provided they are not used to endorse or promote any products or opinions (other than what was expressed by the author) and without taking them out of context. Written permission from the copyright owner must be obtained for everything else.
Original URL of this content is http://www.cyotek.com/blog/manually-writing-the-byte-order-mark-bom-for-an-encoding-into-a-stream?source=rss

ImageBox and TabList update's - virtual mode, pixel grid, bug fixes and more!

$
0
0
A newer version of this code is now available.

Our last post before the new year and some new material is an update to the ImageBox (now at version 1.1.2.0) and TabList (at version 1.0.0.2) controls. You can grab the updated source from the links at the end of the post, or from the GitHub page.

ImageBox

Virtual mode allows you to use the ImageBox control without a backing imageThe pixel grid allows you to show a grid when zoomed in

Changes and new features

  • Added IsPointInImage method. This function returns if a given point is within the image viewport, and is useful for combining with PointToImage.
  • Added ImageBorderColor property, allowing you to customize the color of the image border
  • Added a new ImageBoxBorderStyle, FixedSingleGlowShadow. This style allows for a more smoother outer glow shadow instead of the existing clunky drop shadow.
  • Added ShowPixelGrid and PixelGridColor properties. When set, a dotted grid is displayed around pixels when zooming in on an image.
  • Added new overload to PointToImage which allows you to specify if the function should map the given point to the nearest available edge(s) if the point is outside the image boundaries
  • Added AllowDoubleClick property. When set, the normal double click events and overrides work as expected.
  • Added VirtualMode and VirtualSize properties. These new properties allow you to use all functionality of the ImageBox control without having to set the Image property. You can also use the new VirtualDraw event to provide custom drawing without having to override existing drawing functionality.
  • Additional documentation added via XML comments

Fixed

  • If the GridDisplayMode property is set to Image an explicit image border is no longer drawn, instead the ImageBorder property is correctly honoured.
  • Fixes a problem where half the pixels of the first row/column were lost when zooming. Thanks to Rotem for the fix.
  • The GetImageViewport method now correctly returns a width and height that accounts for control size, padding and zoom levels.
  • Fixed incorrect attributes on AutoSize property
  • Fixed the image viewport sometimes being the incorrect size when zoomed in. Thanks to WMJ for the fix.
  • Fixes "see also" documentation errors for events

TabList

Changes and new features

  • Added ShowTabList property. When set to False, the list of tabs is no longer displayed, and navigation can only occur via code.
  • Added AllowTabSelection property. When set to False, the control can no longer gain focus, mouse hover effects are not displayed, and navigation can only occur via code. This allows you to disable navigation whilst still having the tabs visible.

Fixed

  • Fixed the HoverIndex property always defaulting to zero.

Happy New Year all!

Downloads

All content Copyright © by Cyotek Ltd or its respective writers. Permission to reproduce news and web log entries and other RSS feed content in unmodified form without notice is granted provided they are not used to endorse or promote any products or opinions (other than what was expressed by the author) and without taking them out of context. Written permission from the copyright owner must be obtained for everything else.
Original URL of this content is http://www.cyotek.com/blog/imagebox-and-tablist-updates-virtual-mode-pixel-grid-bug-fixes-and-more?source=rss

Creating a custom ErrorProvider component for use with Windows Forms applications

$
0
0

In recent code, I've been trying to avoid displaying validation errors as message boxes, but display something in-line. The .NET Framework provides an ErrorProvider component which does just this. One of the disadvantages of this control is that it displays an icon indicating error state - which means you need a chunk of white space somewhere around your control, which may not always be very desirable.

This article describes how to create a custom error provider component that uses background colours and tool tips to indicate error state.

A simple application demonstrating the custom provider.

Note: I don't use data binding, so the provider implementation I demonstrate below currently has no support for this.

Getting Started

Create a new Component class and implement the IExtenderProvider interface. This interface is used to add custom properties to other controls - it has a single method CanExtend that must return true for a given source object if it can extend itself to said object.

In this example, we'll offer our properties to any control. However, you can always customize this to work only with certain control types such as TextBoxBase, ListBoxControl etc.

bool IExtenderProvider.CanExtend(object extendee)
{return extendee is Control;
}

Implementing Custom Properties

Unlike how properties are normally defined, you need to create get and set methods for each property you wish to expose. In our case, we'll be offering Error and ErrorBackColor properties. Using Error as an example, the methods would be GetError and SetError. Both methods need to have a parameter for the source object, and the set also needs a parameter for the property value.

Note: I named this property Error so I could drop in replace the new component for the .NET Framework one without changing any code bar the control declaration. If you don't plan on doing this, you may wish to name it ErrorText or something more descriptive!

In this example, we'll store all our properties in dictionaries, keyed on the source control. If you want to be more efficient, rather than using multiple dictionaries you could use one tied to a backing class/structure but we'll keep this example nice and simple.

Below is the implementation for getting the value.

[Category("Appearance"), DefaultValue("")]publicstring GetError(Control control)
{string result;if(control == null)thrownew ArgumentNullException("control");if(!_errorTexts.TryGetValue(control, out result))
    result = string.Empty;return result;
}

Getting the value is straightforward, we attempt to get a custom value from our backing dictionary, if one does not exist then we return a default value.

It's also a good idea to decorate your get methods with Category and DefaultValue attributes. The Category attribute allows you to place the property in the PropertyGrid (otherwise it will end up in the Misc group), while the DefaultValue attribute does two things. Firstly, in designers such as the PropertyGrid, default values appear in a normal type face whilst custom values appear in bold. Secondly, it avoids cluttering up auto generated code files with assignment statements. If the default value is an empty string, and the property is set to that value, no serialization code will be generated. (Which is also helpful if you decide to change default values, such as the default error colour later on)

Next, we have our set method code.

publicvoid SetError(Control control, string value)
{if(control == null)thrownew ArgumentNullException("control");if(value == null)
    value = string.Empty;if(!string.IsNullOrEmpty(value))
  {
    _errorTexts[control] = value;this.ShowError(control);
  }elsethis.ClearError(control);
}

As we want "unset" values to be the empty string, we have a quick null check in place to convert nulls to empty strings. If a non-empty string is passed in, we update the source control to be in it's "error" state. If it's blank, then we clear the error.

protectedvirtualvoid ShowError(Control control)
{if(control == null)thrownew ArgumentNullException("control");if(!_originalColors.ContainsKey(control))
    _originalColors.Add(control, control.BackColor);

  control.BackColor = this.GetErrorBackColor(control);
  _toolTip.SetToolTip(control, this.GetError(control));if (!_erroredControls.Contains(control))
    _erroredControls.Add(control);
}

Above you can see the code to display an error. First we store the original background colour of the control if we haven't previously saved it, and then apply the error colour. And because users still need to know what the actual error is, we add a tool tip with the error text. Finally, we store the control in an internal list - we'll use that later on.

Clearing the error state is more or less the reverse. First we try and set the background colour back it what it's original value, and we remove the tool tip.

publicvoid ClearError(Control control)
{
  Color originalColor;if (_originalColors.TryGetValue(control, out originalColor))
    control.BackColor = originalColor;

  _errorTexts.Remove(control);
  _toolTip.SetToolTip(control, null);
  _erroredControls.Remove(control);
}

Checking if errors are present

Personally speaking, I don't like the built in Validating event as it prevents focus from shifting until you resolve the error. That is a pretty horrible user experience in my view which is why my validation runs from change events. But then, how do you know if validation errors are present when submitting data? You could keep track of this separately, but we might as well get our component to do this.

When an error is shown, we store that control in a list, and then remove it from the list when the error is cleared. So we can add a very simple property to the control to check if errors are present:

publicbool HasErrors
{get { return _erroredControls.Count != 0; }
}

At present the error list isn't exposed, but that would be easy enough to do if required.

Designer Support

If you now drop this component onto a form and try and use it, you'll find nothing happens. In order to get your new properties to appear on other controls, you need to add some attributes to the component.

For each new property you are exposing, you have to add a ProviderProperty declaration to the top of the class containing the name of the property, and the type of the objects that can get the new properties.

[ProvideProperty("ErrorBackColor", typeof(Control)), ProvideProperty("Error", typeof(Control))]publicpartialclass ErrorProvider : Component, IExtenderProvider
{
  ...

With these attributes in place (and assuming you have correctly created <PropertyName>Get and <PropertyName>Set methods, your new component should now start adding properties to other controls in the designer.

Example Usage

In this component validation is done from event handlers - you can either use the built in Control.Validating event, or use the most appropriate change event of your source control. For example, the demo project uses the following code to validate integer inputs:

privatevoid integerTextBox_TextChanged(object sender, EventArgs e)
{
  Control control;string errorText;int value;

  control = (Control)sender;
  errorText = !int.TryParse(control.Text, out value) ? "Please enter a valid integer" : null;

  errorProvider.SetError(control, errorText);
}

privatevoid okButton_Click(object sender, EventArgs e)
{if (!errorProvider.HasErrors)
  {// submit the new datathis.DialogResult = DialogResult.OK;this.Close();
  }elsethis.DialogResult = DialogResult.None;
}

The only thing you need to remember is to clear errors as well as display them!

Limitations

As mentioned at the start of the article, the sample class doesn't support data binding.

Also, while you can happily set custom error background colours at design time, it probably won't work so well if you try and set the error text at design time. Not sure if the original ErrorProvider supports this either, but it hasn't been specifically coded for in this sample as my requirements are to use it via change events of the controls. For this reason, when clearing an error (or all errors), the text dictionary is always updated, but the background colour dictionaries are left alone.

Final words

As usual, this code should be tested before being used in a production application - while we are currently using this in almost-live code, it hasn't been thoroughly tested and may contain bugs or omissions.

The sample project below includes the full source for this example class, and a basic demonstration project.

Downloads

All content Copyright © by Cyotek Ltd or its respective writers. Permission to reproduce news and web log entries and other RSS feed content in unmodified form without notice is granted provided they are not used to endorse or promote any products or opinions (other than what was expressed by the author) and without taking them out of context. Written permission from the copyright owner must be obtained for everything else.
Original URL of this content is http://www.cyotek.com/blog/creating-a-custom-errorprovider-component-for-use-with-windows-forms-applications?source=rss

Dividing up a rectangle based on pairs of points using C#

$
0
0

Recently we released the first alpha of our latest product, Cyotek Slicr, a tool for slicing up an image. At the heart of this tool is a series of routines that take a given image and pairs of input points, from which the image is chopped up accordingly. This article describes how to break up a rectangle into smaller parts based on user defined co-ordinates.

An example of the programs output

Caveat Emptor

Before I get started, I just want to point out that the code I'm about to show you is proof of concept code. It doesn't use algorithms such as Bentley–Ottmann, it's not very efficient, and there's probably a hundred ways of doing it better. However, it works, which is pretty much all I care about at this moment!

Getting Started

These are the rules for splitting up a rectangle into component parts:

  1. Lines must be straight, so each pair of co-ordinates will align on one axis
  2. Co-ordinates must start from either an edge, or the intersection of another pair. The second co-ordinate must end in a similar fashion. Any "orphan" co-ordinates which don't start/end on another edge/join are illegal and should be ignored
  3. Co-ordinates can start and end at any point in the bounds of the canvas, as long as they follow the previous rules.

In order to achieve this, I ended up with creating a number of support classes

  • Segment - this class starts the starting point of a line, it's length, and it's orientation. Originally I just started by storing the two pairs of co-ordinates, but in the end it was easier with the length and orientation.
  • SegmentPoint - this class stores the details of a single point. An instance of this class is created for each unique point created by the start and end of a segment, and any intersections. It also stores the directions of neighbouring points.
internalclass Segment
{public Point EndLocation
  {get
    {int x;int y;switch (this.Orientation)
      {case SegmentOrientation.Vertical:
          x = this.Location.X;
          y = this.Location.Y + this.Size;break;default:
          x = this.Location.X + this.Size;
          y = this.Location.Y;break;
      }returnnew Point(x, y);
    }
  }public Point Location { get; set; }public SegmentOrientation Orientation { get; set; }publicint Size { get; set; }
}internalclass SegmentPoint
{public SegmentPointConnections Connections { get; set; }public Point Location { get; set; }publicint X { get { returnthis.Location.X; } }publicint Y { get { returnthis.Location.Y; } }
}

With these helper classes, I can now construct the code to process them and extract the rectangles.

Calculating Points

In this example, a rectangle has been created by segments crossing each other. We need to detect the intersections to find these types of rectangles.

The image above demonstrates the first problem. The four segments intersect with each other, causing a rectangle to be generated untouched by any user defined point. However, if we are going to find that rectangle, we need to find any new point generated by multiple segments intersecting.

privatevoid CalculatePoints()
{
  List&lt;Segment> segments;

  segments = new List&lt;Segment>();this.Points = new Dictionary&lt;Point, SegmentPoint>();// add segments representing the edges
  segments.Add(new Segment { Location = Point.Empty, Size = this.Size.Width, Orientation = SegmentOrientation.Horizontal });
  segments.Add(new Segment { Location = new Point(0, this.Size.Height), Size = this.Size.Width, Orientation = SegmentOrientation.Horizontal });
  segments.Add(new Segment { Location = Point.Empty, Size = this.Size.Height, Orientation = SegmentOrientation.Vertical });
  segments.Add(new Segment { Location = new Point(this.Size.Width, 0), Size = this.Size.Height, Orientation = SegmentOrientation.Vertical });// add the rest of the segments
  segments.AddRange(this.Segments);

  segments.Sort((a, b) =>
  {int result = a.Location.X.CompareTo(b.Location.X);if (result == 0)
      result = a.Location.Y.CompareTo(b.Location.Y);return result;
  });foreach (Segment segment in segments)
  {
    Segment currentSegment;// add the segment pointsthis.UpdatePoint(segment.Location, segment.Orientation == SegmentOrientation.Horizontal ? SegmentPointConnections.Left : SegmentPointConnections.Top);this.UpdatePoint(segment.EndLocation, segment.Orientation == SegmentOrientation.Horizontal ? SegmentPointConnections.Right : SegmentPointConnections.Bottom);// calculate any intersecting points
    currentSegment = segment;foreach (Segment otherSegment in segments.Where(s => s != currentSegment))
    {
      Point intersection;

      intersection = Intersection.FindLineIntersection(segment.Location, segment.EndLocation, otherSegment.Location, otherSegment.EndLocation);
      if (!intersection.IsEmpty)
      {
         SegmentPointConnections flags;

        flags =  SegmentPointConnections.None;
        if (intersection != segment.Location && intersection != segment.EndLocation)
        {if (segment.Orientation == SegmentOrientation.Horizontal)
            flags |= ( SegmentPointConnections.Left | SegmentPointConnections.Right);else
            flags |= (SegmentPointConnections.Top | SegmentPointConnections.Bottom);
        }elseif (intersection != otherSegment.Location && intersection != otherSegment.EndLocation)
        {if (otherSegment.Orientation == SegmentOrientation.Horizontal)
            flags |= (SegmentPointConnections.Left | SegmentPointConnections.Right);else
            flags |= (SegmentPointConnections.Top | SegmentPointConnections.Bottom);
        }if (flags != SegmentPointConnections.None)this.UpdatePoint(intersection, flags);
      }
    }
  }
}

Breaking the code down, we do the following:

  • Create an additional four segments representing the boundaries of the canvas
  • Sort the segments by their starting locations
  • Cycle each segment and
    • Create a point for the starting co-ordinate
    • Create a point for the ending co-ordinate
    • Cycle each other segment and see if it intersects with the current segment. If it does, create a new point at the intersection

In any case above where I state to create a point, the code will either create a point if one doesn't already exist, otherwise it will update the connections of the existing point.

privatevoid UpdatePoint(Point location, SegmentPointConnections connections)
{
  SegmentPoint point;if (!this.Points.TryGetValue(location, out point))
  {
    point = new SegmentPoint { Location = location, Connections = connections };this.Points.Add(point.Location, point);
  }elseif (!point.Connections.HasFlag(connections))
    point.Connections |= connections;
}

The CalculatePoints method above is very inefficient, but it does the job. Once this routine has run, we'll have an array of co-ordinates and the directions of linked points.

Calculating Rectangles

Now that we have all points, both user defined, and intersections, we can use this to generate the actual rectangles.

privatevoid CalculateRectangles()
{
  SegmentPoint[] horizontalPoints;
  SegmentPoint[] verticalPoints;this.Rectangles = new HashSet&lt;Rectangle>();
  horizontalPoints = this.Points.Values.OrderBy(p => p.X).ToArray();
  verticalPoints = this.Points.Values.OrderBy(p => p.Y).ToArray();foreach (SegmentPoint topLeft inthis.Points.Values.Where(p => p.Connections.HasFlag(SegmentPointConnections.Left | SegmentPointConnections.Top)))
  {
    SegmentPoint topRight;
    SegmentPoint bottomLeft;

    topRight = horizontalPoints.FirstOrDefault(p => p.X > topLeft.X && p.Y == topLeft.Y && p.Connections.HasFlag(SegmentPointConnections.Right | SegmentPointConnections.Top));
    bottomLeft = verticalPoints.FirstOrDefault(p => p.X == topLeft.X && p.Y > topLeft.Y && p.Connections.HasFlag(SegmentPointConnections.Left | SegmentPointConnections.Bottom));if (topRight != null&& bottomLeft != null)
    {
      SegmentPoint bottomRight;

      bottomRight = horizontalPoints.FirstOrDefault(p => p.X == topRight.X && p.Y == bottomLeft.Y && p.Connections.HasFlag(SegmentPointConnections.Right | SegmentPointConnections.Bottom));if (bottomRight != null)
      {
        Rectangle rectangle;

        rectangle = new Rectangle(topLeft.X, topLeft.Y, bottomRight.X - topLeft.X, bottomRight.Y - topLeft.Y);this.Rectangles.Add(rectangle);
      }
    }
  }
}

In this method, we loop through all our defined points that have connections in the upper left corner.

For each matching point, we then try and find points with the following criteria

  • has the same Y co-ordinate and a right or top connection. This gives us the upper right corner.
  • has the same X co-ordinate and a left or bottom connection. This gives us the lower left corner.
  • if we have both the above corners, we then try and find a point that has the same X co-ordinate as the upper right corner, the same Y co-ordinate as the lower left corner, and right or bottom connections. This gives us the last corner, lower right
  • if we have all four corners, and the rectangle. The use of a HashSet ensures the same rectangle isn't added twice.

And that's all there is to it. With these two routines, I can now break up a rectangle into many smaller pieces just by defining pairs of points.

Another example of the programs output

Closing Remarks

There are a few things that I'm aware of that the code doesn't do

  • As mentioned (several times!) none of this code is particularly efficient. The more segments you add, the slower it will get. Gareth Rees posted a nice diagram of what I should be doing, and indeed his pointers help me get this code working originally.
  • It doesn't handle overlapping segments very well. It will rerun the point generation for these, adding to the overall time.
  • Ordering of the output rectangles isn't always what you expect - it jumps around a bit
  • The end coordinate must be equal to or greater than the start (using the sample, providing a negative segment size would trigger this bug).

As always the source code for this sample can be downloaded from the link below.

Downloads

All content Copyright © by Cyotek Ltd or its respective writers. Permission to reproduce news and web log entries and other RSS feed content in unmodified form without notice is granted provided they are not used to endorse or promote any products or opinions (other than what was expressed by the author) and without taking them out of context. Written permission from the copyright owner must be obtained for everything else.
Original URL of this content is http://www.cyotek.com/blog/dividing-up-a-rectangle-based-on-pairs-of-points-using-csharp?source=rss

ColorPicker Controls for Windows Forms

$
0
0
A newer version of this code is now available.

Back at the start of the new millennium, I had a publishing agreement with another company to publish our components under their branding. The first of these components was the ColorPicker ActiveX control. Roll on 13 years later and that publishing agreement is long expired, ActiveX is dead, and yet here I am again writing a color picker control. Except this time losing money rather than making it. There's probably a life lesson buried in there somewhere.

All of our current products ask for a color at least once (mostly buried in an options dialog), and some of the prototype products we are working on ask for more. Currently, we just wrap around the System.Drawing.Design.ColorEditor class, which overtime has identified a few problems:

  • Keyboard support is iffy (this is more to do with how it's implemented at our end I suspect)
  • You can't choose alpha channels, or enter custom values
  • The dependency on System.Design prevents us from targeting the Client Profile, and indeed will cause a crash if deployed to a system with only the Client Profile installed

The other option briefly considered was just to replace with the standard color common dialog. But this dialog looks and operates the same as it did back in Windows 3, and while that is pretty good from a consistent UI standpoint, it does mean it hasn't kept up with changing times - such as entering colors as hex values. I took a look at some other third party libraries but none of them were flexible enough for what I wanted.

In the end I went with writing my own set of C# based color picker controls which we're providing here as open source - we hope you find them useful. As there's quite a lot of code, I'm not going to describe them line by line as I've done in the past, but just provide an overview of each component.

ColorGrid Control

ColorGrid control demonstration

This control displays a grid of colors, and supports both a primary palette, and a custom color palete. Several properties are available for configuring the appearing of the control, and there are behaviour options too, such as built in editing of colors.

ColorWheel Control

ColorWheel control demonstration

This control displays a radial wheel of colors and allows selection from any point in the wheel. Not much in the way of customisation for this control!

ColorSlider Controls

ColorSlider controls demonstration

A bunch of controls (inheriting from a single base) that allow selection of values via a colourful bar. Similar to the TrackBar control you have a few options for specifying the drag handle's position and bar orientation.

ColorEditor Control

ColorEditor controls demonstration

This control allows you enter/select a color using RGB/HSL or hex formats.

ScreenColorPicker Control

ScreenColorPicker controls demonstration

This control allows the user to pick a color from any pixel displayed on the screen.

ColorPickerDialog Form

ColorPickerDialog demonstration

This form puts together the previous controls in a ready to use dialog.

ColorEditorManager

This is a non-GUI component that you can drop onto a form, and bind to other controls in this library. When the Color property of one control changes, it is reflected in the others without having to lift a finger. Useful if you're creating composite displays from multiple controls.

Color Palettes

The ColorGrid control has Colors and CustomColors properties which return a ColorCollection. These two properties make it easier as a developer to keep separate a primary palette whilst having the flexibility of custom colors, although it does complicate the control's internal logic a bit! The grid will automatically populate custom colors if you try and set the control's Color to a value not currently defined.

As well as manually populating ColorCollection instances, you can also load in external palette files. Paint.NET and the age old JASC 1.0 formats are currently supported.

Keyboard Support

All GUI components, with the exception of the ScreenColorPicker include full keyboard/focus support. Many controls support SmallChange and LargeChange properties which influence how navigation keys are processed. Although in the case of the ColorWheel it's not really a bonus... but that's what the ColorEditor control is best suited for!

Known Issues

  • XML documentation comments are incompleted
  • The ColorEditorManager control currently allows you to bind to the LightnessColorSlider control, but doesn't fully support it yet

Acknowledgements

Source Code

Download the source code from the link below, or from the GitHub page.

All content Copyright © by Cyotek Ltd or its respective writers. Permission to reproduce news and web log entries and other RSS feed content in unmodified form without notice is granted provided they are not used to endorse or promote any products or opinions (other than what was expressed by the author) and without taking them out of context. Written permission from the copyright owner must be obtained for everything else.
Original URL of this content is http://www.cyotek.com/blog/colorpicker-controls-for-windows-forms?source=rss


ColorPicker Controls Update 1.0.2.0

$
0
0

I've been pretty busy recently pushing out updates to WebCopy, a pending update to Spriter and working on a game project so blog posts have suffered a bit. While I work to correct that, we've just pushed an update to the ColorPicker controls.

Important!

This update contains breaking changes due to a number of renamed classes and enum members.

So what's new?

  • All the somethingPaletteReader classes have been replaced with somethingPaletteSerializer. As the new names imply, palettes can now be written as well as read. This is a breaking change and may require some reworking of any code that used to use the old readers.
  • The ColorEditor now supports selecting of named colors as the hex editor is now a dropdown list. As well as being able to select named colors from the list, you can now also type names directly into the hex editor and they will be processed accordingly.
  • The ColorPickerDialog now can load and save palette files
  • Palette support has been reworked to allow the saving of palettes as well as loading. Unfortunately due to the initial names of these classes this is a breaking change, albeit a minor one.
  • Added a bit more documentation
  • Corrected some grammatical errors in existing documentation and headers
  • Added additional tests to ensure palettes are written correctly

New from 1.0.1.0:

  • GIMP Palette support
  • Some unit tests to make sure palettes are read correctly

Named color support

Support for loading and saving palettes

Downloads

Grab the update from the link below or from the GitHub page.

We hope you enjoy these improvements!

Downloads

All content Copyright © by Cyotek Ltd or its respective writers. Permission to reproduce news and web log entries and other RSS feed content in unmodified form without notice is granted provided they are not used to endorse or promote any products or opinions (other than what was expressed by the author) and without taking them out of context. Written permission from the copyright owner must be obtained for everything else.
Original URL of this content is http://www.cyotek.com/blog/colorpicker-controls-update-1-0-2-0?source=rss

Creating a custom TypeConverter part 1 - getting started

$
0
0

It is common practice to create a class that describes something, a person, a product - some entity or other. Your application may provide a sublime UI for editing these objects, or rely on something more basic such as a PropertyGrid. However, if you use this approach, you may find that some of your properties can't be edited. Yet examples abound of non-simple editing via the grid, such as colours, enumerations and image selection to name a few.

By making use of the TypeConverter and UITypeEditor classes, you can quite easily provide the ability to create richer editing support for your objects. This first article in this series will detail how to use TypeConverter allowing complex objects to be edited as though they were simple strings.

The Scenario

As with most of my articles, I'm starting with a real world example and a solid required. I need to store units of measurement, so for this I have a simple class that has a pair of properties describing a given measurement.

publicclass Length
{publicoverridestring ToString()
  {string value;string unit;

    value = this.Value.ToString(CultureInfo.InvariantCulture);
    unit = this.Unit.ToString();returnstring.Concat(value, unit);
  }public Unit Unit { get; set; }publicfloat Value { get; set; }
}

A fairly standard class that simply has two properties along with a default (implicit) constructor. I'm also overriding ToString, as it's useful both for debugging purposes and for having something other than CustomTypeConverter1.Length displayed in the PropertyGrid.

And for the purposes of this demonstration, I have created a sample class which has three length properties.

internalclass SampleClass
{public Length Length1 { get; set; }public Length Length2 { get; set; }public Length Length3 { get; set; }
}

Just for completeness sake, here's the Unit enum.

publicenum Unit
{
  None,
  cm,
  mm,
  pt,
  px
}

Isn't that an ugly enum? For this example, it will suffice, but there is another article which describes an alternative approach.

First Steps

I've set up a sample project which binds an instance of our SampleClass to a PropertyGrid, with the Length1 property pre-set to 32px. When you run this project, you are left with a very unsatisfactory editing experience as you can't edit anything.

Default editing functionality... or lack thereof

So, what can we do about this?

The TypeConverterAttribute Class

The TypeConverterAttribute allows you to associate your class with a type that can handle conversion of instances of your type to and from other objects. You can only have one occurrence of this attribute per type. As with a lot of these types of attributes, you can provide the conversion type one of two ways:

[TypeConverter(typeof(LengthConverter))]

Here, we pass in a type object, meaning the type has to be directly referenced by your project and distributed as a dependency.

[TypeConverter("CustomTypeConverter1.LengthConverter, CustomTypeConverter1")]

Another alternative is to use a direct string, as shown above. This string is the fully qualified type name, meaning it could be located in a differently assembly, but one that isn't referenced directly or flagged as a dependency.

Which one you use depends on your needs, but bear in mind no compile time checking can be done of the string version, so if you get the name wrong, you won't find out until you are unable to edit the type!

The ExpandableObjectConverter

This class is built into the .NET Framework and will provide a minimum of functionality at minimum cost.

[TypeConverter(typeof(ExpandableObjectConverter))]publicclass Length
{

If we change the declaration of our Length class to be the above and run our sample, we get this:

A little better, as long as you don't mind editing each property individually

The first property can now be expanded, and each property of the Length class can be individually set. However, there are two immediate problems with this approach:

  • Properties can only be edited one at a time, you can't combine values via the root property.
  • Properties with a null value (the second and third properties in the example screenshot) cannot be instantiated.

Again, depending on your requirements, this might be perfectly acceptable. In my case, it isn't, so on with the custom converter!

Writing a custom converter

In order to create a custom converter, you need to have a class which inherits from TypeConverter. At a minimum, you would override the CanConvertFrom and ConvertFrom methods.

Here's a sample converter for our simple Length class:

publicclass LengthConverter : TypeConverter
{publicoverridebool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
  {return sourceType == typeof(string) || base.CanConvertFrom(context, sourceType);
  }publicoverrideobject ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
  {string stringValue;object result;

    result = null;
    stringValue = value asstring;if (!string.IsNullOrEmpty(stringValue))
    {int nonDigitIndex;

      nonDigitIndex = stringValue.IndexOf(stringValue.FirstOrDefault(char.IsLetter));if (nonDigitIndex > 0)
      {
        result = new Length
        {
          Value = Convert.ToSingle(stringValue.Substring(0, nonDigitIndex)),
          Unit = (Unit)Enum.Parse(typeof(Unit), stringValue.Substring(nonDigitIndex), true)
        };
      }
    }return result ?? base.ConvertFrom(context, culture, value);
  }
}

So, what is this short class doing?

The first override, CanConvertFrom, is called when .NET wants to know if it can convert from a given type. Here, I'm saying "if you are a string, then yes I can convert" (or at least try!), otherwise it falls back and requests if the base converter can do the conversion. In most cases that'll probably be a "no", but it's probably a good idea to leave it in regardless;

Now for the interesting method. ConvertFrom does the type conversion. I'm going to ignore the context parameter for now as I haven't had a need for it. You can use the culture parameter as a guide if you need to do any conversions such as numbers or dates. The key parameter, is value as this contains the raw data to convert.

  • The first thing this method does is see if value is a non-null non-empty string. (If you're using .NET 4 or above you'd probably use the IsNullOrWhitespace method instead).
  • Next I try and find the index of the first letter character - the method assumes the input is in the form of <number><unit>.
  • If I find a letter, then I create a new Length object and use object initialization to set the Value property to be the first part of the string converted to a float, and Enum.Parse to set the Unit property using the latter part of the string. And that explains the horribly named enum. I'll still show you a better way though!

And that is all you need. Well almost, we need to change our class header:

[TypeConverter(typeof(LengthConverter))]publicclass Length
{

Now when we run the sample project, we can directly type in a value into the different Length based properties and have them converted to the correct values, including creating new values.

With the custom type converter, we can now directly enter units of measurement

Note that this example doesn't cover clearing a value - for example if you enter an empty string. You could return a new Length object in this case and then change the ToString method to return an empty string. Simply returning null from ConvertFrom doesn't actually work, so at the moment I don't know the best method for accomplishing a value reset.

Error Handling

I haven't demonstrated error handling, firstly as this is a bare bones example, and also due to .NET providing it for you, at least in the case of the property grid. It will automatically handle the failure to convert a value. The disadvantage is the rather unhelpful error message. If you throw an exception yourself, the exception text you provide is displayed in the Details section of the dialog, allowing you to specifying a more succinct message.

Generic error messages aren't the best - do your users a favour and provide detailed exception text

Converting to a different data type

As well as converting a type into our class, we can also use a type converter to convert our class into another type by overriding the ConvertTo method.

In this example, the Length class overrides the ToString method. I would still recommend doing that in additional to this next tip, but as with everything, it depends on your purpose. In this case, we can use the ConvertTo method to convert our Length object into a string.

publicoverrideobject ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
  Length length;object result;

  result = null;
  length = value as Length;if (length != null&& destinationType == typeof(string))
    result = length.ToString();return result ?? base.ConvertTo(context, culture, value, destinationType);
}

As with all the methods you override, if you can't explicitly handle the passed values, then ask the base class to attempt to handle it. The above method shows how I check to ensure the value is a Length object, and then if the destinationType is a string, I simply return value.ToString(). Whatever is returned via this method will appear in the PropertyGrid, so use caution if you decide to return formatted strings - you'll need to handle them in ConvertFrom.

There is another, more useful, purpose for this override, but I'll defer that for the next article.

Summing up

Adding a basic type converter is a very simple thing to do, and is something that can help enrich editing functionality, or even debugging (in lieu of an immediate window or scripting support, Cyotek products have a sample add-in which displays documents in a PropertyGrid for simple editing and querying). Even if you only go as far as adding the ExpandableObjectConverter attribute to a base class, it's more useful than nothing!

You can download the complete example from the link below.

Downloads

All content Copyright © by Cyotek Ltd or its respective writers. Permission to reproduce news and web log entries and other RSS feed content in unmodified form without notice is granted provided they are not used to endorse or promote any products or opinions (other than what was expressed by the author) and without taking them out of context. Written permission from the copyright owner must be obtained for everything else.
Original URL of this content is http://www.cyotek.com/blog/creating-a-custom-typeconverter-part-1?source=rss

Creating a custom TypeConverter part 2 - Instance descriptors, expandable properties and standard values

$
0
0

In the first part of this article series, I described how to create a simple type converter for converting an object to and from a string. This follow up article expands upon that sample, to include more concise design time code generation, expandable property support and finally custom lists of values.

The examples in this article assume you are working from the original sample project from part 1.

Designer Code

When you place a Control or Component onto a design time surface such as a Form, the IDE will automatically generate any code required to initialize the object.

Modify the SampleClass class to inherit from Component then drop an instance onto the form and set the first property. Save the form, then open the designer file. You should see code something like this:

privatevoid InitializeComponent()
{
  CustomTypeConverter2.Length length1 = new CustomTypeConverter2.Length();// ... SNIP ...// // sample// 
  length1.Unit = CustomTypeConverter2.Unit.px;
  length1.Value = 32F;this.sample.Length1 = length1;this.sample.Length2 = null;this.sample.Length3 = null;// ... SNIP ...

}

The designer has generated the source code required to populate the object by specifying each property individually. However, what happens if you wanted to set both properties at once or perhaps perform some other initialization code? We can use our type converter to solve this one.

Although slightly outside the bounds of this article, it's probably worth mentioning nonetheless. In the snippet above, you can see the Length2 and Length3 properties are explicitly assigned null, even though that is already the default value of these properties. If you're creating public facing library components it's always a good idea to apply the DefaultValue attribute to properties. It makes for cleaner code (if the value is the default value, no code will be generated) and allows other components to perform custom processing if required. For example, the PropertyGrid shows default properties in normal style, and non-default ones in bold.

Updating the Length class

Before we can adjust our type converter to support code generation, we need to extend our Length class by adding a new constructor.

public Length()
{ }public Length(float value, Unit unit)
  : this()
{this.Value = value;this.Unit = unit;
}

I've added one constructor which will set both Value and Unit properties of the class. Due to the addition of a constructor with parameters, I now need to explicitly define a parameterless constructor as an implicit one will no longer be generated and I still want to be able to do new Length().

With these modifications in place, we can now dive into the type converter modifications.

CanConvertTo

The first thing we need to do is update our type converter to state that it supports the InstanceDescriptor class which is the mechanism the IDE will use for the custom code generation. We can do this by overriding a new method, CanConvertTo.

Update the LengthConverter class from the previous article to include the following:

publicoverridebool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{return destinationType == typeof(InstanceDescriptor) || base.CanConvertTo(context, destinationType);
}

This new overloads will inform the caller that we now support the InstanceDescriptor type, in addition to whatever the base TypeConverter can handle.

Extending ConvertTo

We briefly covered the ConvertTo override in the previous article in order to display our Length object as a string. Now that we have overridden CanConvertTo to state that we can handle additional types, we need to update this method as well.

The InstanceDescriptor class contains information needed to regenerate an object, and is comprised of two primary pieces of information.

  • A MemberInfo object which describes a method in the class. This can either be a constructor (which we'll use in our example), or something static that will return a new object - for example, Color.FromArgb.
  • An ICollection containing any of the arguments required to pass into the source member.

Lets update ConvertTo to include the extract support.

publicoverrideobject ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
  Length length;object result;

  result = null;
  length = value as Length;if (length != null)
  {if (destinationType == typeof(string))
      result = length.ToString();elseif (destinationType == typeof(InstanceDescriptor))
    {
      ConstructorInfo constructorInfo;

      constructorInfo = typeof(Length).GetConstructor(new[] { typeof(float), typeof(Unit) });
      result = new InstanceDescriptor(constructorInfo, newobject[] { length.Value, length.Unit });
    }
  }return result ?? base.ConvertTo(context, culture, value, destinationType);
}

We still do our null check to ensure we have a valid value to convert, but now we check to see if the type is either string or InstanceDescriptor and process accordingly.

For instance descriptors, we use Reflection in order to get the constructor which takes two parameters, and then we create an InstanceDescriptor object from that. Easy enough!

Now when we modify our SampleClass component in the designer, source code is generated similar to the below. (With the caveat of the warning in the next section)

Note that I'd also modified the properties on the SampleClass to include [DefaultValue(typeof(Length), "")] for default value support.

privatevoid InitializeComponent()
{// ... SNIP ...// // sample// this.sample.Length1 = new CustomTypeConverter2.Length(16F, CustomTypeConverter2.Unit.px);// ... SNIP ...

}

Much cleaner!

A warning on Visual Studio

While writing this article, Visual Studio frequently took a huff and refused to generate the design time code. I assume it is due to Visual Studio caching the assembly containing the TypeConverter, or it is another manifestation of not being able to unload managed assemblies without destroying the application domain. Whatever the reason, I found it quickly to be a source of frustration requiring frequent restarts of the IDE in order to pick up changed code.

As an experiment, I did a test where the Length and LengthConverter classes were in another assembly referenced in binary form. In this mode, I didn't have a single problem.

Finally, whereas basic conversions are easy to debug, the InstanceDescriptor conversion is much less so.

Something to bear in mind.

Expandable properties

Returning to the ExpandableObjectConverter and property expansion, that is trivially easy to add to your custom converter by overriding the GetPropertiesSupported and GetProperties methods.

publicoverridebool GetPropertiesSupported(ITypeDescriptorContext context)
{returntrue;
}publicoverride PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object value, Attribute[] attributes)
{return TypeDescriptor.GetProperties(value, attributes);
}

First, by overriding GetPropertiesSupported we tell the caller that we support individual property editing. Then we can override GetProperties to return the actual properties to display.

In the above example, we return all available properties, which is probably normal behaviour. Let us assume the Length class has a property on it which we didn't want to see. We could return a different collection with that property filtered out:

publicoverride PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object value, Attribute[] attributes)
{//return TypeDescriptor.GetProperties(value, attributes);returnnew PropertyDescriptorCollection(TypeDescriptor.GetProperties(value, attributes).Cast&lt;PropertyDescriptor>().Where(p => p.Name != "BadProperty").ToArray());
}

An awkward example, but it does demonstrate the feature.

The property grid honours the Browsable attribute - this is a much better way of controlling visibility of properties than the above!

Custom Values

The final example I want to demonstrate is custom values. Although you might assume that you'd have to create a custom UITypeEditor, if you just want a basic drop down list, you can do this directly from your type converter by overriding GetStandardValuesSupported and GetStandardValues.

publicoverridebool GetStandardValuesSupported(ITypeDescriptorContext context)
{returntrue;
}publicoverride TypeConverter.StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
{
  List&lt;Length> values;

  values = new List&lt;Length>();
  values.Add(new Length(16, Unit.px));
  values.Add(new Length(32, Unit.px));
  values.Add(new Length(64, Unit.px));
  values.Add(new Length(128, Unit.px));returnnew StandardValuesCollection(values);
}

First you need to override GetStandardValuesSupported in order to specify that we do support such values. Then in GetStandardValues we simply return the objects we want to see. In this example, I've generate 4 lengths which I return. When you run the program, you can see and select these values. Of course, you need to make sure that the values you return can be handled by the ConvertFrom method!

A simple example of adding some standard values

Summing up

Adding even an advanced type converter is still a easy task, and is something that can help enrich editing functionality.

You can download the complete example from the link below.

Downloads

All content Copyright © by Cyotek Ltd or its respective writers. Permission to reproduce news and web log entries and other RSS feed content in unmodified form without notice is granted provided they are not used to endorse or promote any products or opinions (other than what was expressed by the author) and without taking them out of context. Written permission from the copyright owner must be obtained for everything else.
Original URL of this content is http://www.cyotek.com/blog/creating-a-custom-typeconverter-part-2?source=rss

Using alternate descriptions for enumeration members

$
0
0

The last two articles (here and here) described creating a custom type converter for converting units of measurement.

However, what happens when you want to display or convert to/from alternative representations? For example, consider the enum below.

internalenum Unit
{
  cm,
  mm,
  pt,
  px
}

Apart from the fact such an enum more than likely doesn't match any coding standards you use, what happens when you want to include percentages in the mix? Not many languages are going to let you use % as a symbol name!

So we rewrite the enum to make more sense, in which case you might have this:

internalenum Unit
{
  Centimetre,
  Millimetre,
  Point,
  Pixel,
  Percent
}

Using the actual enum member names doesn't seem like a good idea does it?

Great! Except... your users want to see cm, px, % etc. Now what?

The manual way

Well, you could create a function which takes a unit, and manually checks the values and returns an appropriate value, for example:

publicstring GetUnitSuffix(Unit unit)
{string result;switch (unit)
  {case Unit.Centimetre:
      result = "cm";break;
    ...
  }return result;
}

While this would certainly work, it means you have to duplicate this code for every enum you wish to have alternate descriptions for. Not to mention, should you add a new member to the enum, you have to remember to update this function. More than likely, you also want a sister version of this function which accepts the string version, and returns the enum value.

The automatic way

A better way would be to tag each enum member with an appropriate description, then you can use reflection to scan your enum members and perform automatic to and from conversions.

In this example, I'm going to use the DescriptionAttribute from the System.ComponentModel namespace, although depending on what you're trying to do, a custom attribute may be better - that's not exactly what this attribute was intended for!

First, decorate your enum with the attribute.

internalenum Unit
{
  [Description("cm")]
  Centimetre,

  [Description("mm")]
  Millimetre,

  [Description("pt")]
  Point,

  [Description("px")]
  Pixel,

  [Description("%")]
  Percent
}

Next add a couple of functions that will perform the conversion of your enum to and from a string. With this in place you can add new members, and, as long as you add your attribute to them, the functions will automatically handle the new values.

publicstaticstring GetDescription(this Unit value)
{
  FieldInfo field;
  DescriptionAttribute attribute;string result;

  field = value.GetType().GetField(value.ToString());
  attribute = (DescriptionAttribute)Attribute.GetCustomAttribute(field, typeof(DescriptionAttribute));
  result = attribute != null ? attribute.Description : string.Empty;return result;
}publicstatic Unit GetValue(string value)
{
  Unit result;

  result = Unit.None;

  foreach (Unit id in Enum.GetValues(typeof(Unit)))
  {
    FieldInfo field;
    DescriptionAttribute attribute;

    field = id.GetType().GetField(id.ToString());
    attribute = Attribute.GetCustomAttribute(field, typeof(DescriptionAttribute)) as DescriptionAttribute;if (attribute != null&& attribute.Description == value)
    {
      result = id;break;
    }
  }return result;
}

I choose to make the version that accepts the enum member as an input parameter an extension method, that way I can call it like this:

string unitSuffix;

unitSuffix = this.Unit.GetDescription();

However, as the sister method accepts a string parameter, it doesn't make sense to make this an extension, unless you want it to appear on every single string variable you declare! So I just revert back to the usual static calling convention.

Unit unit;

unit = EnumExtensions.GetValue(stringValue.Substring(nonDigitIndex));

Much better - the user sees the short form version, but the code uses the full name

Using Generics

While there's nothing wrong with the above methods, they could still be improved upon. As it stands now, the methods are fixed to a specific enum, so we can change them to use generics instead, then they'll work for all enums.

publicstaticstring GetDescription&lt;T>(this T value)where T : struct
{
  FieldInfo field;
  DescriptionAttribute attribute;string result;

  field = value.GetType().GetField(value.ToString());
  attribute = (DescriptionAttribute)Attribute.GetCustomAttribute(field, typeof(DescriptionAttribute));
  result = attribute != null ? attribute.Description : string.Empty;return result;
}publicstatic T GetValue&lt;T>(string value, T defaultValue)
{
  T result;

  result = defaultValue;

  foreach (T id in Enum.GetValues(typeof(T)))
  {
    FieldInfo field;
    DescriptionAttribute attribute;

    field = id.GetType().GetField(id.ToString());
    attribute = Attribute.GetCustomAttribute(field, typeof(DescriptionAttribute)) as DescriptionAttribute;if (attribute != null&& attribute.Description == value)
    {
      result = id;break;
    }
  }return result;
}

Final points

Using reflection does have an overhead. If you expect to be calling these methods a lot, you may wish to extend them yet further in order to support caching the results in a dictionary or other mechanism of your choice. That way, the first time a new member is requested you perform the reflection lookup, and thereafter just read the cache. I haven't done any benchmarking, but it's probably safe to say a dictionary lookup (remember to use TryGetValue!) is going to be a lot faster than a reflection scan.

An example showing how the custom type converter from the previous two articles updated to use the above technique is available from the link below.

Downloads

All content Copyright © by Cyotek Ltd or its respective writers. Permission to reproduce news and web log entries and other RSS feed content in unmodified form without notice is granted provided they are not used to endorse or promote any products or opinions (other than what was expressed by the author) and without taking them out of context. Written permission from the copyright owner must be obtained for everything else.
Original URL of this content is http://www.cyotek.com/blog/using-alternate-descriptions-for-enumeration-members?source=rss

ImageBox 1.1.4.0 update

$
0
0
A newer version of this code is now available.

Today we've released a new updated version of the ImageBox control, with a nice collection of enhancements and a few bug fixes.

Full change log for this update:

Changes and new features

  • Added NuGet package
  • Added a license file to hopefully cut down on questions about usage. The ImageBox control is licensed under the MIT license, allowing you free reign to use it in your projects, commercial or otherwise. See imagebox-license.txt for the full text.
  • Added a new SizeMode property. This allows you to switch between Normal, Fit and Stretch modes. Stretch is a new mode for the ImageBox, and acts similar to existing Fit functionality except the aspect ratio is not preserved.
  • The SizeToFit property has been marked as deprecated and should no longer be used. The SizeMode property has a Fit value that should be used instead. Setting the SizeToFit property will now manipulate SizeMode instead.
  • Added a new CenterPoint property. This property returns the pixel at the center of the current image viewport.
  • Added a bunch of missing XML comments documentation.
  • Added new overloads for most methods that accepted a source Rectangle, Point or Size to also accept float and int arguments.
  • Added a new Zoomed event that uses new ImageBoxZoomEventArgs arguments. This new event allows you to tell if the zoom was in or out, how it was raised, and current and previous zoom values. Not hugely thrilled with how aspects of this change has been internally implemented, so implementation methods are private rather than virtual so I can change them without affecting the signature.
  • Added new CenterToImage method which resets the viewport to be centered of the image, in the same way as zooming via the keyboard used to work.
  • Added support for animated GIF's, thanks to a contribution from Eggy. Note animations only play at runtime, not design time.
  • The Text and Font properties are now available and, if set, will be displayed in the control. You can use the ForeColor, TextBackColor, TextAlign, TextDisplayMode and ScaleText properties to determine how the text will be rendered.
  • A new DrawLabel method that performs text drawing is available for use by custom implementations or virtual modes.

Demonstration Changes

  • Added a new Scaled Adornments demonstration, showing how easy it is to add custom drawing that is scaled and positioned appropriately.
  • Added a new Switch Image During Zoom demonstration, a demo with an unwieldy name that shows how to switch out a low resolution image with a higher detailed one as you zoom into an ImageBox.
  • Added new Text and Size Mode demonstrations.

Bug Fixes

  • Zooming in and out with the keyboard now keeps the view centered to the same pixel that was centered prior to the zoom
  • Zooming in and out with the keyboard is now correctly disabled if the AllowZoom property is False, or the SizeMode property is a value other than Normal. This means keyboard behaviour now matches mouse behaviour.
  • If the mouse wheel was rapidly spun (thus having a multiple of the base delta), the Zoom property was only adjusted once
  • Setting the GridScale property to None rendered the default Small grid. Using a scale of None now correctly just fills the grid area with a solid brush from the GridColor property.
  • The MouseWheel event is now available
  • Layout changes no longer occur if the AllowPainting property is false through use of the BeginUpdate method.
  • Fixed various documentation errors

Downloads

As usual, either grab the source from GitHub, from the link below, or make use of the NuGet package!

Downloads

All content Copyright © by Cyotek Ltd or its respective writers. Permission to reproduce news and web log entries and other RSS feed content in unmodified form without notice is granted provided they are not used to endorse or promote any products or opinions (other than what was expressed by the author) and without taking them out of context. Written permission from the copyright owner must be obtained for everything else.
Original URL of this content is http://www.cyotek.com/blog/imagebox-1-1-4-0-update?source=rss

Viewing all 559 articles
Browse latest View live