Archive

Archive for the ‘WPF’ Category

Customize SpellCheck in WPF textbox

June 22nd, 2010 Anuraj P No comments

While working on a personal project (fleetIt). I worked on Textbox spell check option. I enabled it, it was working fine, until I added a context menu to to the textbox, for custom commands of my application. It was displaying the spelling errors but the suggestions and correction options was not available. Then I tried to customize the context menu such a way that it can display both my custom options as well as the system default spell check options. Here is a simple implementation, which helps to customize the context menu and display the spell check suggestions options.

<Window x:Class="dotnetthoughts.net.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:sys="clr-namespace:System;assembly=System"
Title="WPF SpellCheck Demo" Height="350" Width="525" Loaded="Window_Loaded">
<DockPanel>
<TextBox Name="txtEdit" SpellCheck.IsEnabled="True"
                AcceptsReturn="True"
ContextMenuOpening="txtEdit_ContextMenuOpening"
                VerticalScrollBarVisibility="Visible">
<TextBox.ContextMenu>
<ContextMenu Name="ctxMenu" />
</TextBox.ContextMenu>
</TextBox>
</DockPanel>
</Window>

And here is the code behind

namespace dotnetthoughts.net
{
    using System;
    using System.IO;
using System.Linq;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Documents;
    using System.Windows.Input;

public partial class MainWindow : Window
    {
public MainWindow()
        {
InitializeComponent();
        }

private void txtEdit_ContextMenuOpening(object sender, ContextMenuEventArgs e)
        {
            int index = 0;
this.txtEdit.ContextMenu.Items.Clear(); //Clearing the existing items
            //Getting the spellcheck suggestions.
SpellingError spellingError = this.txtEdit.GetSpellingError(this.txtEdit.CaretIndex);
if (spellingError != null && spellingError.Suggestions.Count() >= 1)
            {
                //Creating the suggestions menu items.
foreach (string suggestion in spellingError.Suggestions)
                {
MenuItem menuItem = new MenuItem();
                    menuItem.Header = suggestion;
menuItem.FontWeight = FontWeights.Bold;
menuItem.Command = EditingCommands.CorrectSpellingError;
                    menuItem.CommandParameter = suggestion;
menuItem.CommandTarget = this.txtEdit;
this.txtEdit.ContextMenu.Items.Insert(index, menuItem);
                    index++;
                }
Separator seperator = new Separator();
this.txtEdit.ContextMenu.Items.Insert(index, seperator);
                index++;
//Adding the IgnoreAll menu item
MenuItem IgnoreAllMenuItem = new MenuItem();
                IgnoreAllMenuItem.Header = "Ignore All";
IgnoreAllMenuItem.Command = EditingCommands.IgnoreSpellingError;
IgnoreAllMenuItem.CommandTarget = this.txtEdit;
                this.txtEdit.ContextMenu.Items.Insert(index, IgnoreAllMenuItem);
                index++;
            }
            else
            {
//No Suggestions found, add a disabled NoSuggestions menuitem.
                MenuItem menuItem = new MenuItem();
menuItem.Header = "No Suggestions";
                menuItem.IsEnabled = false;
this.txtEdit.ContextMenu.Items.Insert(index, menuItem);
                index++;
            }
//.Net 4.0 Supports CustomDictionaries, Option for Adding to dictionary.
int selectionStart = this.txtEdit.GetSpellingErrorStart(this.txtEdit.CaretIndex);
            if (selectionStart >= 0)
            {
                Separator seperator1 = new Separator();
this.txtEdit.ContextMenu.Items.Insert(index, seperator1);
                index++;
MenuItem AddToDictionary = new MenuItem();
                AddToDictionary.Header = "Add to Dictionary";
                //Getting the word to add
this.txtEdit.SelectionStart = selectionStart;
this.txtEdit.SelectionLength = this.txtEdit.GetSpellingErrorLength(this.txtEdit.CaretIndex);
                //Ignoring the added word.
AddToDictionary.Command = EditingCommands.IgnoreSpellingError;
AddToDictionary.CommandTarget = this.txtEdit;
AddToDictionary.Click += (object o, RoutedEventArgs rea) =>
                {
this.AddToDictionary(this.txtEdit.SelectedText);
                };
this.txtEdit.ContextMenu.Items.Insert(index, AddToDictionary);
                index++;
            }

//Common Edit MenuItems.
            Separator seperator2 = new Separator();
this.txtEdit.ContextMenu.Items.Insert(index, seperator2);
            index++;
            //Cut
MenuItem cutMenuItem = new MenuItem();
cutMenuItem.Command = ApplicationCommands.Cut;
this.txtEdit.ContextMenu.Items.Insert(index, cutMenuItem);
            index++;
            //Copy
MenuItem copyMenuItem = new MenuItem();
copyMenuItem.Command = ApplicationCommands.Copy;
this.txtEdit.ContextMenu.Items.Insert(index, copyMenuItem);
            index++;
            //Paste
MenuItem pasteMenuItem = new MenuItem();
pasteMenuItem.Command = ApplicationCommands.Paste;
this.txtEdit.ContextMenu.Items.Insert(index, pasteMenuItem);
            index++;
            Separator seperator3 = new Separator();
this.txtEdit.ContextMenu.Items.Insert(index, seperator3);
            index++;
            //Delete
MenuItem deleteMenuItem = new MenuItem();
deleteMenuItem.Command = ApplicationCommands.Delete;
            this.txtEdit.ContextMenu.Items.Insert(index, deleteMenuItem);
            index++;
            Separator seperator4 = new Separator();
this.txtEdit.ContextMenu.Items.Insert(index, seperator4);
            index++;
            //Select All
MenuItem selectAllMenuItem = new MenuItem();
selectAllMenuItem.Command = ApplicationCommands.SelectAll;
            this.txtEdit.ContextMenu.Items.Insert(index, selectAllMenuItem);
            index++;
        }
        //Method to Add text to Dictionary
private void AddToDictionary(string entry)
        {
using (StreamWriter streamWriter = new StreamWriter(@"D:\WPF\MyCustomDictionary.lex", true))
            {
streamWriter.WriteLine(entry);
            }
        }

private void Window_Loaded(object sender, RoutedEventArgs e)
        {
//Assigning custom Dictionary to TextBox
this.txtEdit.SpellCheck.CustomDictionaries.Add(new Uri(@"D:\WPF\MyCustomDictionary.lex"));
        }
    }
}

The .Net Framework 4.0 supports CustomDictionaries, which helps to create your own Dictionary.

//.Net 4.0 Supports CustomDictionaries, Option for Adding to dictionary.
int selectionStart = this.txtEdit.GetSpellingErrorStart(this.txtEdit.CaretIndex);
if (selectionStart >= 0)
{
    Separator seperator1 = new Separator();
this.txtEdit.ContextMenu.Items.Insert(index, seperator1);
    index++;
MenuItem AddToDictionary = new MenuItem();
    AddToDictionary.Header = "Add to Dictionary";
    //Getting the word to add
this.txtEdit.SelectionStart = selectionStart;
this.txtEdit.SelectionLength = this.txtEdit.GetSpellingErrorLength(this.txtEdit.CaretIndex);
    //Ignoring the added word.
AddToDictionary.Command = EditingCommands.IgnoreSpellingError;
AddToDictionary.CommandTarget = this.txtEdit;
AddToDictionary.Click += (object o, RoutedEventArgs rea) =>
    {
this.AddToDictionary(this.txtEdit.SelectedText);
    };
this.txtEdit.ContextMenu.Items.Insert(index, AddToDictionary);
    index++;
}

Here is the screenshot of Demo Application.

WPF Spell Check Demo - Screenshot

WPF Spell Check Demo - Screenshot

Categories: .Net, .Net 3.0 / 3.5, WPF Tags: , , , ,

Custom Places in FileDialog box

June 21st, 2010 Anuraj P No comments

If you ever tried to Open a File from Visual Studio, you may notice something like Projects Folder in the Open File Dialog.

Open File Dialog in Visual Studio

Open File Dialog in Visual Studio

We can also implement the same functionality in our applications by using CustomPlaces collection property of FileDialog class. The OpenFileDialog and SaveFileDialog classes allow you to add folders to the CustomPlaces collection.

openFileDialog.CustomPlaces.Add(@"C:\Users");

You can also specify GUID of Windows Vista known folder. Known Folder GUIDs are not case sensitive and are defined in the KnownFolders.h file in the Windows SDK. If the specified Known Folder is not present on the computer that is running the application, the Known Folder is not shown.

openFileDialog.CustomPlaces.Add(@"C:\Users");
//Desktop Folder
openFileDialog.CustomPlaces.Add(new Guid("B4BFCC3A-DB2C-424C-B029-7FE99A87C641"));
//Downloads Folder
openFileDialog.CustomPlaces.Add(new Guid("374DE290-123F-4565-9164-39C4925E467B"));
Open File Dialog with Custom Places

Open File Dialog with Custom Places

Note: This feature will not have any effect in Windows XP. Also you must set the AutoUpgradeEnabled to True(default) to enable this feature in Vista or Windows 7.

The following table lists few Windows Vista Known Folders and their associated Guid.

  1. Contacts : 56784854-C6CB-462B-8169-88E350ACB882
  2. ControlPanel : 82A74AEB-AEB4-465C-A014-D097EE346D63
  3. Desktop : B4BFCC3A-DB2C-424C-B029-7FE99A87C641
  4. Documents : FDD39AD0-238F-46AF-ADB4-6C85480369C7
  5. Downloads : 374DE290-123F-4565-9164-39C4925E467B
  6. Favorites : 1777F761-68AD-4D8A-87BD-30B759FA33DD
  7. Fonts : FD228CB7-AE11-4AE3-864C-16F3910AB8FE
  8. Music : 4BD8D571-6D19-48D3-BE97-422220080E43

System.InvalidOperationException – Cross-thread operation not valid

February 5th, 2010 Anuraj P 2 comments

System.InvalidOperationException – Cross-thread operation not valid: Control ‘xxxxx’ accessed from a thread other than the thread it was created on. Normally we get this exception when we try to modify some control property from another thread. This is because when a program executes the operating system will assign a thread for the creation of UI elements and for the changes of the UI. Only this thread has got the permission to change or control UI elements created. If you creates other threads with the help of Thread class from System.Threading namespace, doesn’t have enough privileges to change or control the UI elements of the Main thread. To resolve this issue, we need write delegates and need to invoke the methods from other threads using these delegates.

This sample code is doing some long operation in a separate thread and try to update the UI from the new thread.


private void Form1_Load(object sender, EventArgs e)
{
    this.threadStart = new ThreadStart(LongProcess);
    this.thread = new Thread(this.threadStart);
    this.thread.Start();
}
private void LongProcess()
{
    for (int i = 0; i < 100; i++)
    {
        Thread.Sleep(100);
        //This will throw an exception like this
        //System.InvalidOperationException - Cross-thread operation not valid:
        //Control 'textBox1' accessed from a thread other than the thread it was created on.
        this.textBox1.Text = i.ToString();
    }
}

And here is the source code which will fix the issue with delegates and Control.Invoke method

private ThreadStart threadStart;
private Thread thread;
private delegate void UpdateTextDelegate(string text);
private void Form1_Load(object sender, EventArgs e)
{
    this.threadStart = new ThreadStart(LongProcess);
    this.thread = new Thread(this.threadStart);
    this.thread.Start();
}

private void LongProcess()
{
    for (int i = 0; i < 100; i++)
    {
        Thread.Sleep(100);
        //This will resolve the cross thread invalid operation exception.
        this.UpdateText(i.ToString());
    }
}

private void UpdateText(string text)
{
    //Checks whether the called required to invoke the "Invoke" method.
    if (this.textBox1.InvokeRequired)
    {
        UpdateTextDelegate updateTextDelegate = new UpdateTextDelegate(UpdateText);
        //Calling the invoke method of the control with the parameter.
        this.textBox1.Invoke(updateTextDelegate, new object[] { text });
    }
    else
    {
        this.textBox1.Text = text;
    }
}

Thanks to Praveen for helping me out. Happing Coding :)

EDIT: In case WPF applications, we don’t have InvokeRequired property available for Controls. Instead we need to use Dispatcher.CheckAccess() method. We can rewrite the UpdateText method like the following

private void UpdateText(string text)
{
if (this.textbox1.Dispatcher.CheckAccess())
{
    this.textbox1.Content = text;
}
else
{
UpdateTextDelegate updateTextHandler = new UpdateTextDelegate(UpdateText);
this.textbox1.Dispatcher.Invoke(updateTextHandler, new object[] { text });
}
}

Thanks to Sivadas for his valueable comments.

WPF interoperability with Windows Forms

November 2nd, 2009 Anuraj P No comments

In October 24th 2009, I got a chance to attend MS Community Techdays at Trivandrum. One of the session was WPF interoperability with Windows Forms. By using this we can use WPF controls in Classic Windows based applications. For this you require a special control called “ElementHost“, if you developed any Windows based application in VS 2008, this control will be available under WPF interoperability tab. If you add this control to a Windows Form, Visual Studio will automatically updates the References list. It will add few more references like PresentationCore, PresentationFrameWork, UIAutomationProvider, WindowsBase, WindowsFormsIntegration. Then you can add a WPF User Control to a Windows Form.

  1. Create a Windows Application Project.
  2. Right Click on the Project Node in the solution explorer, select Add New Item, and Select User Control(WPF) from Add New Item Dialog.
  3. In the User Contol I wrote some code to change the Color on MouseMove and I wrote the click event in the Codebehind.
  4. XAML Code

    <Grid>
        <Grid.Resources>
            <Style TargetType="Button" x:Key="CustomButton">
                <Setter Property="FontSize" Value="15" />
                <Style.Triggers>
                    <Trigger Property="IsMouseOver" Value="True">
                        <Setter Property="FontSize" Value="20" />
                    </Trigger>
                </Style.Triggers>
            </Style>
        </Grid.Resources>
        <Button Style="{StaticResource CustomButton}"
                Content="Hello World"
                Click="Button_Click">
        </Button>
    </Grid>
    

    Code behind – C#

    private void Button_Click(object sender, RoutedEventArgs e)
    {
    	MessageBox.Show("WPF UserControl Event");
    }
    
  5. Build the Project.
  6. Drag and Drop, ElementHost control from WPF interoperability tab.
  7. You can Set the host the WPF usercontrol either by selecting Select Host Content from Smart Menu, or by Setting the Child Property in the Property List. (The control will be populated in the List only if you build the application, after you added the control.)
  8. You can also do it in runtime by setting Child Property of the Element Host control.
  9. private void Form1_Load(object sender, EventArgs e)
    {
            SampleWPFCtrl ctrl1 = new SampleWPFCtrl();
                this.elementHost1.Child = ctrl1;
    }
    
    WPF Usercontol Control in Windows Form

    WPF Usercontol Control in Windows Form

    Happy Coding :)

How to Store and Retrieve files from SQL Server Database

October 7th, 2009 Anuraj P No comments

The forum I joined recently got lot of queries like How to Save Images in the Database, How to save files in SQL Server, How read files from Database etc. So I thought of writing a post regarding this. Even though I am part of a Web project, I am doing some Windows applications for the client. So I thought it will nice to brush-up the ASP.Net skills.

Here is the code. I am using SQL Server 2008, but I am not using FileStream for the current project, I already wrote a post to how to
manage files with FileStream feature. In this post I am using nvarchar(MAX) datatype for storing the file content.

Table Design

CREATE TABLE [dbo].[tblFiles](
	[FileId] [uniqueidentifier] NOT NULL,
	[Filename] [nvarchar](255) NOT NULL,
	[FileContent] [varbinary](max) NULL
)

And the I set FileId default to newId() and FileContent default to NULL

ALTER TABLE [dbo].[tblFiles] ADD  CONSTRAINT [DF_tblFiles_FileId]  DEFAULT (newid()) FOR [FileId]
GO
ALTER TABLE [dbo].[tblFiles] ADD  CONSTRAINT [DF_tblFiles_FileContent]  DEFAULT (NULL) FOR [FileContent]
GO

I wrote the code in C#. I am having a Asp FileUpload control and a button to upload the file, and a Repeater control with two controls, a label for displaying the filename and hyper link control for downloading the file.

<body>
<form runat="server">
<asp:FileUpload runat="server" ID="fileUploadImage" />
<asp:Button runat="server" ID="cmdUpload" Text="Upload File" OnClick="cmdUpload_Click" />
<asp:Repeater runat="server" ID="rptrFiles">
    <HeaderTemplate>
        <table>
    </HeaderTemplate>
    <ItemTemplate>
        <tr>
            <td>
                <asp:Label runat="server" ID="lblFilename" Text='<%# Eval("FileName")%>' />
            </td>
            <td>
              <asp:HyperLink runat="server" Target="_blank" ID="lbtDownload" Text="Download" NavigateUrl='<%# "Download.aspx?File="  + Eval("FileId").ToString() %>' />
            </td>
        </tr>
    </ItemTemplate>
    <FooterTemplate>
        </table>
    </FooterTemplate>
</asp:Repeater>
</form>
</body>

Uploading the File to the Database
Code behind

protected void cmdUpload_Click(object sender, EventArgs e)
{
    string fileName = Path.GetFileName(this.fileUploadImage.FileName);
    byte[] fileContent = this.fileUploadImage.FileBytes;
    using (SqlConnection connection = new SqlConnection("Server=.\\SQLEXPRESS;User Id=sa;Password=sapassword;Database=sampledb"))
    {
        connection.Open();
        using (SqlCommand command = new SqlCommand("INSERT INTO tblFiles(Filename, FileContent) VALUES(@Filename, @FileContent)", connection))
        {
            SqlParameter fileNameParameter = new SqlParameter("@Filename", System.Data.SqlDbType.NVarChar, 255);
            fileNameParameter.Value = fileName;
            SqlParameter fileContentParameter = new SqlParameter("@FileContent", System.Data.SqlDbType.VarBinary);
            fileContentParameter.Value = fileContent;
            command.Parameters.AddRange(new SqlParameter[] { fileNameParameter, fileContentParameter });
            command.ExecuteNonQuery();
        }
    }
}

And here is code to bind the repeater from the Database

DataTable dtFiles = new DataTable("Files");
using (SqlDataAdapter adapter = new SqlDataAdapter("SELECT FileId, FileName FROM tblFiles", "Server=.\\SQLEXPRESS;User Id=sa;Password=sapassword;Database=sampledb"))
{
    adapter.Fill(dtFiles);
}
this.rptrFiles.DataSource = dtFiles;
this.rptrFiles.DataBind();

Download / Read the file from Database
And to download / read the file from Database, I am passing the File unique id to another page(download.aspx).I this page I am checking for the File querysting and based on that reading filecontent from Sql and writing it to Asp.net output stream. You can get more information about how to download files from IIS in this post.

protected void Page_Load(object sender, EventArgs e)
{
    if (Request.QueryString["File"] != null)
    {
        string fileId = Request.QueryString["File"];
        using (SqlConnection connection = new SqlConnection("Server=.\\SQLEXPRESS;User Id=sa;Password=sapassword;Database=Sample"))
        {
            connection.Open();
            using (SqlCommand command = new SqlCommand("SELECT Filename, FileContent FROM tblFiles WHERE FileId = @FileId", connection))
            {
                command.Parameters.AddWithValue("@FileId", fileId);
                SqlDataReader reader = command.ExecuteReader(CommandBehavior.CloseConnection);
                if (reader.HasRows)
                {
                    reader.Read();
                    byte[] content = reader["FileContent"] as byte[];
                    string filename = reader["FileName"].ToString();
                    Response.Clear();
                    Response.ClearContent();
                    Response.AddHeader("Content-Disposition", "attachment; filename=" + filename);
                    Response.AddHeader("Content-Length", content.Length.ToString());
                    Response.OutputStream.Write(content, 0, content.Length);
                    Response.End();
                }
            }
        }
    }
}

Please write to me if I missed something. Happy Programming .

Consuming a C++ DLL in C#

July 10th, 2009 Anuraj P No comments

While working on my current project, I had to use some low level I/O operations, but it was difficult using C#, and I got some C++ implementations. Then I thought of creating a DLL in C++ and use it in C#, but I didn’t get any code for the implementation. So I done some searching and I found a solution. It is not a complete solution, but it works :)

Creating a DLL using C++
For creating a DLL in C++, I was used cl.exe, which comes with .net framework. For the implementation I just wrote simple C++ file.(Simple.cpp)

#include <iostream>

extern "C" __declspec(dllexport) char* Hello();
char* Hello()
{
	return "Hello world";
}

I think this is pretty much clear.The extern “C” __declspec(dllexport) allows generate export names automatically. For more details :Exporting from a DLL Using __declspec(dllexport)(MSDN)

After creating this Simple.cpp file, go to Visual Studio Tools > Visual Studio 2008 Command Prompt. Go to the location where you have stored the Simple.cpp file and for compiling and linking C++ file you can use “cl.exe /LD Simple.cpp”.(For more details about cl.exe options checkout : Compiler Options Listed Alphabetically(MSDN) It will compile and Link, and gives a DLL as output.

Consuming a C++ library in C#
When I try to add the DLL by Add Reference, Visual Studio will not allows to add C++ library as Reference. So I tried it with Interop option, by using DLLImportAttribute.

[DllImport(@"D:\Simple.dll", EntryPoint = "Hello")]
public extern static string Hello();

Then you can call this function in C# like the following

private void button1_Click(object sender, EventArgs e)
{
            MessageBox.Show(Hello());
}

It will display a Messagebox with “Hello World”. Thats it you consumed a C++ DLL in C#.

Issue in the implementation

  1. I can’t use the DllImport function without the full location. To avoid this I tried to Register the DLL using RegSvr32.But I got some error from RegSvr like this.
  2. The module “D:\Simple.dll” was loaded but the entry-point DllRegisterServer was not found.

    Make sure that “D:\Simple.dll” is a valid DLL or OCX file and then try again.

I still exploring the things, I will update once I got the solution for this. Happy Coding :)