Archive for February, 2008

My Visual Studio Colour Scheme

Friday, February 8th, 2008

In his recent post Scott is listing various Visual Studio themes. He also mentions a positive impact of good colour schemes on productivity.

I can certainly confirm this. I switched to a custom theme several month ago and I’m never going back.

Visual Studio Colour Scheme

Unfortunately I can’t remember which scheme I used as a base, but it must have been an offspring of Vibrant Ink. The code sample is by Jeff Atwood.

SQL Server: Full-Text Searching Across Multiple Tables

Wednesday, February 6th, 2008

Sometimes a full-text search needs to be performed on multiple tables. For example, if we have two tables, Post and Comment, we may want to search for posts that have keywords both in the post text and in the comments.

SQL Server doesn’t allow to include multiple tables in the CONTAINS and FREETEXT predicates. We can of course use several predicates in our SQL query, but this can get pretty complex if we need to search in many columns.

Luckily there’s a workaround. Starting from version 2005, SQL Server allows to add FTS indices to views. Knowing this, we can create a view that combines text columns from both tables and then search on it.

There are a few caveats:

  1. The view must be created with the SCHEMABINDING option.
  2. Tables in the view must be referenced by their schema name, e.g. dbo.Post
  3. Before adding an FTS index, the view must have a unique clustered index. This itself limits the kinds of views that can be indexed. Among other things, the SELECT statement of the view cannot contain: UNION operator, sub-queries, derived tables, OUTER JOINs, some aggregate functions, rowset functions, etc.
  4. The FTS index cannot be added on text, ntext, image and xml columns. Thankfully, it can still be added on varchar(max), nvarchar(max) and varbinary(max) columns, introduced in version 2005.

Here is how our fictitious example can be implemented.

The tables:

CREATE TABLE Post
(
    ID int NOT NULL IDENTITY(1,1) PRIMARY KEY,
    Text nvarchar(max) NOT NULL
)
CREATE TABLE Comment
(
    ID int NOT NULL IDENTITY(1,1) PRIMARY KEY,
    PostID int NOT NULL FOREIGN KEY REFERENCES Post(ID),
    Text nvarchar(max) NOT NULL
)

The view and its unique index:

CREATE VIEW ViewSearch WITH SCHEMABINDING AS
SELECT c.ID, p.Text AS PostText, c.Text AS CommentText
FROM dbo.Comment AS c
JOIN dbo.Post AS p ON p.ID=c.PostID
GO
CREATE UNIQUE CLUSTERED INDEX IX_ViewSearch ON ViewSearch (ID)

After the full-text catalog is created and the index is added on the view, we can use this query:

SELECT DISTINCT p.ID
FROM Post AS p
JOIN Comment AS c ON c.PostID=p.ID
JOIN ViewSearch AS s ON s.ID=c.ID
WHERE CONTAINS(s.*, ‘foo’) AND CONTAINS(s.*, ‘bar’)

Unfortunately a single CONTAINS(s.*, ‘"foo" AND "bar"’) won’t work, SQL Server always uses OR when CONTAINS is applied to multiple columns.

If you have a keen eye you may have noticed a problem in the solution above. It doesn’t work for posts that have no comments at all. Ideally we would need to use a LEFT JOIN in our FTS view, but the SQL Server wouldn’t let us to create a unique index on it.

A possible workaround is to add a full-text index on Post.Text and to rewrite the query like this:

SELECT p.ID
FROM Post AS p
JOIN Comment AS c ON c.PostID=p.ID
JOIN ViewSearch AS s ON s.ID=c.ID
WHERE CONTAINS(s.*, ‘foo’) AND CONTAINS(s.*, ‘bar’)
UNION
SELECT p.ID
FROM Post AS p
WHERE CONTAINS(p.Text, ‘"foo" AND "bar"’)

Editing icons of WinForms controls.

Friday, February 1st, 2008

I love icons! I use them all the time in my WinForms applications - anywhere from buttons and tab pages to tool strip items. Icons look nice (at least they should be), but editing them is not.

In WinForms you need to edit the original image and change the corresponding .Image or .Icon properties everywhere that image is used. Some controls, such as the TabPage, can only refer to an ImageList, so you would need to amend it and be extra careful not to screw up the image indices.

ImageResourceI wrote a small component to ease the process (of editing, not screwing up, mind you). When ImageResource is dropped into a form it adds a new string property to Forms, Buttons, TabPages and ToolStripItems. You just need to enter the resource name of the icon and the rest is done automatically.

Here’s the code:

using System;
using System.ComponentModel;
using System.Reflection;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
using System.IO;

namespace Versia.Samples
{
    [ProvideProperty("ImageResource", typeof(object))]
    public partial class ImageResource : Component, IExtenderProvider
    {
        private readonly Dictionary<object, string> _values = new Dictionary<object, string>();
        private readonly Dictionary<string, Icon> _icons = new Dictionary<string, Icon>();

        public ImageResource()
        {
            InitializeComponent();
        }

        public ImageResource(IContainer container)
        {
            container.Add(this);

            InitializeComponent();
        }

        public bool CanExtend(object target)
        {
            return target is ButtonBase || target is TabPage || target is Form || target is ToolStripItem;
        }

        [Category("Appearance")]
        [Description("Sets the resource name of the image to add to the control.")]
        [DefaultValue("")]
        public string GetImageResource(object target)
        {
            string value;
            if(_values.TryGetValue(target, out value))
            {
                return value;
            }
            return String.Empty;
        }

        public void SetImageResource(object target, string name)
        {
            _values[target] = name;

            if(!_imageList.Images.ContainsKey(name))
            {
                // Get the assembly from the resource name.
                Stream stream = null;
                foreach(Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
                {
                    try
                    {
                        stream = assembly.GetManifestResourceStream(name);
                    }
                    catch(NotSupportedException)
                    {
                    }
                    catch(FileNotFoundException)
                    {
                    }

                    if(stream != null) break;
                }

                if(stream == null) return;

                // Read the image from the resource.
                if(name.EndsWith(".ico", StringComparison.CurrentCultureIgnoreCase))
                {
                    Icon icon = new Icon(stream);
                    _icons[name] = icon;
                    _imageList.Images.Add(name, icon);
                }
                else
                {
                    _imageList.Images.Add(name, Image.FromStream(stream));
                }
            }
            // Add the image to the control.
            if(target is ButtonBase)
            {
                ButtonBase button = (ButtonBase)target;
                button.Image = _imageList.Images[name];
            }
            else if(target is TabPage)
            {
                // ImageList is attached to the TabControl.
                TabPage tabPage = target as TabPage;
                if(tabPage.Parent != null)
                {
                    ((TabControl)tabPage.Parent).ImageList = _imageList;
                    tabPage.ImageKey = name;
                }
                else
                {
                    tabPage.ParentChanged += delegate(object sender, EventArgs e)
                    {
                        TabPage page = (TabPage)sender;
                        if(page.Parent is TabControl)
                        {
                            ((TabControl)page.Parent).ImageList = _imageList;
                            page.ImageKey = name;
                        }
                    };
                }
            }
            else if(target is Form)
            {
                Form form = (Form)target;
                if(_icons.ContainsKey(name)) form.Icon = _icons[name];
            }
            else if(target is ToolStripItem)
            {
                ToolStripItem item = (ToolStripItem)target;
                item.Image = _imageList.Images[name];
            }
        }
    }
}