65.9K
CodeProject 正在变化。 阅读更多。
Home

符号插入器

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.94/5 (23投票s)

2012年7月30日

CPOL

5分钟阅读

viewsIcon

41312

downloadIcon

1447

一款应用程序,可让您在手指不离开键盘的情况下输入您最喜欢的十个符号。

引言

在我开始开发时,键盘上缺少几个符号(如 © 和 ☺)让我尤其恼火。这款应用程序是我最终为解决这个问题而创建的解决方案。

我写这篇文章时,先创建了一个可视化界面,然后将代码插入后台使其工作——因此我以这种格式呈现这篇文章。

应用程序的目标

该应用程序的基本目标如下:

  • 尽量模仿操作系统外观和感觉。
  • 实现键盘操作功能。这意味着在键盘上为应用程序注册一个热键,并使用键盘上的数字键来选择符号、关闭应用程序以及将符号插入您的文档、代码等。
  • 允许用户自定义符号。
  • 在用户登录时自动运行应用程序,这样就避免了每次都要启动它的麻烦。
  • 在任务栏中保留一个通知图标,允许用户关闭应用程序。

这是应用程序单次使用的摘要。

创建可视化界面

命令链接和模仿操作系统外观和感觉

这些控件可以在 Microsoft API Code Pack 中找到。只需下载它们并将它们放入您的工具箱。应用程序底部沿着的灰色条由两个面板组成,它们的坞设置为底部。颜色是通过使用 Paint 的“取色器”工具在 IE9 下载对话框的屏幕截图中获得的。

设置对话框

在设置对话框中,用户可以更改其符号收藏夹的内容以及它们的顺序。他们还可以设置应用程序在登录时启动。

这里的代码相当直接。也许唯一值得评论的部分是符号的上移和下移。

private void btnUp_Click(object sender, EventArgs e)
{
    if (lstCollection.SelectedIndex != 0 && lstCollection.SelectedIndex != -1)
    {
        int point = lstCollection.SelectedIndex;
        char s = (char)lstCollection.SelectedItem;
        lstCollection.Items.RemoveAt(point);
        lstCollection.Items.Insert(point - 1, s);
        lstCollection.SelectedIndex = point - 1;
    }
}
 
private void btnDown_Click(object sender, EventArgs e)
{
    if (lstCollection.SelectedIndex != lstCollection.Items.Count - 1 && 
                  lstCollection.SelectedIndex != -1)
    {
        int point = lstCollection.SelectedIndex;
        char s = (char)lstCollection.SelectedItem;
        lstCollection.Items.RemoveAt(point);
        lstCollection.Items.Insert(point + 1, s);
        lstCollection.SelectedIndex = point + 1;
    }
}

任务栏图标

这相当基础。这段代码在窗体的构造函数中。

//Setup the tasktray icon
icon.Icon = Properties.Resources.Symbol;
icon.Visible = true;
icon.ContextMenu = new ContextMenu();
icon.ContextMenu.MenuItems.Add("Show Dialog", ShwDlg);
icon.ContextMenu.MenuItems.Add("About", Abt);
icon.ContextMenu.MenuItems.Add("-");
icon.ContextMenu.MenuItems.Add("Exit", Exit);
icon.MouseUp += new MouseEventHandler(icon_MouseUp);

请注意,可以通过插入“-”作为菜单项来创建换行。关于使用 NotifyIcon 的一个警告是,当您关闭应用程序时,该图标可能会在您的任务栏中停留一段时间,直到您鼠标悬停。这个问题可以通过此重写来解决:

protected override void OnFormClosing(FormClosingEventArgs e)
{
    if (e.CloseReason == CloseReason.UserClosing && !AppClosingDown)
    {
        //Don't close, hide.
        e.Cancel = true;
        
        //Act as if the user clicked the Cancel button.
        btnCancel_Click(this, e);
    }
    else
    {
        icon.Visible = false;
        icon.Dispose();
    }
 
    base.OnFormClosing(e);
}

另外,请注意,如果用户单击窗体的关闭按钮,窗体会拒绝关闭调用而是隐藏。

加载单选按钮并处理主窗体

加载单选按钮相当直接。这是在窗体构造函数中运行的基本代码。单选按钮是设计器中预先存在的。

//Process the radiobuttons.
int foundsofar = 0;
foreach (Control c in this.Controls)
{
    if (c.ToString().Contains("System.Windows.Forms.RadioButton"))
    {
         //Add a click handler
         c.Click += new EventHandler(c_Click);
 
         //Set the symbol
         c.Text = Properties.Settings.Default.Symbols[foundsofar].ToString();
 
         //If it was π make the font Arial Rounded MT Bold - looks better.
         if (c.Text == "π")
             c.Font = new Font("Arial Rounded MT Bold", c.Font.Size);
 
         //For the next control discovered.
         ++foundsofar;
    }
}

然后,c_Click 的代码是这样的:

void c_Click(object sender, EventArgs e)
{
    //We don't need to worry if the user
    //changes their mind because the timer immediately stops
    //if the form has already extended.
    tmAnim.Start();
}

计时器的滴答事件是这样的:

private void tmAnim_Tick(object sender, EventArgs e)
{
    if (this.Height != 318)
    {
        this.Top -= 5;
        this.Height += 10;
    }
    else
    {
        tmAnim.Stop();
    }
}

因此,当用户单击任何一个单选按钮时,窗体就会滑出并显示如上图所示。

创建操作

可视化界面就讲完了!现在我将总结应用程序是如何实际工作的。

用户登录时启动

这需要一些注册表编辑。这篇文章是我的灵感来源。

首先,您需要这些值:

private const string RegistryPath = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run";
private const string KeyName = "JohnsonSymbolInserter";

当然,您可以将 KeyName 更改为您想要的任何名称。以下是基本代码摘要:

//Get value
RegistryKey rk = Registry.CurrentUser.OpenSubKey(RegistryPath);
bool isRunningOnLogin = (rk.GetValue(KeyName) != null);
 
//Set as yes
RegistryKey rk = Registry.CurrentUser.OpenSubKey(RegistryPath, true);
rk.SetValue(KeyName, Application.ExecutablePath);
 
//Set as no
RegistryKey rk = Registry.CurrentUser.OpenSubKey(registryPath, true);
rk.DeleteValue(KeyName);
 
//Flip value
RegistryKey rk = Registry.CurrentUser.OpenSubKey(registryPath, true);
if (rk.GetValue(KeyName) == null)
   rk.SetValue(KeyName, Application.ExecutablePath);
else
   rk.DeleteValue(KeyName);

注册热键

这篇文章(又来了!)是我实现热键的来源。基本上,我们需要这些外部调用:

[DllImport("user32.dll")]
private static extern int RegisterHotKey(IntPtr hwnd, int id, uint mod, Keys k);
 
[DllImport("user32.dll")]
private static extern int UnregisterHotKey(IntPtr hwnd, int id);

我们还需要这些常量(您也可以为 Ctrl、Alt 等注册热键,有关 RegisterHotKey 的更多信息,请参见 此处)。

private const int ID = 0xFDAE;
private const int WindowsKey = 0x0008;

然后您需要像下面这样重写 OnHandleCreatedOnHandleDestroyedWndProc

protected override void OnHandleCreated(EventArgs e)
{
    base.OnHandleCreated(e);
 
    //The hot key...
    try
    {
        RegisterHotKey(this.Handle, ID, WindowsKey, Keys.S);
    }
    catch
    {
        MessageBox.Show("The application failed to register the Windows+S HotKey. " + 
          "The application will now exit.", "Register Failed", 
          MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
        Application.Exit();
    }
}
 
protected override void OnHandleDestroyed(EventArgs e)
{
    base.OnHandleDestroyed(e);
 
    //The hot key...
    try
    {
        UnregisterHotKey(this.Handle, ID);
    }
    catch
    {
        //Hmm...
    }
}
 
protected override void WndProc(ref Message m)
{
    if (m.Msg == 0x0312)
    {
        this.ShowForm();
    }
 
    base.WndProc(ref m);
}

发送符号 - 方法一

插入符号是此应用程序的主要目标……它是如何实现的?

最容易实现的方法是将您选择的符号复制到剪贴板,如下所示:

//Hide the form and reset the height
this.HideForm();
 
//Get the selected symbol
string selected = "";
foreach (Control c in this.Controls)
{
    if (c.ToString().Contains("System.Windows.Forms.RadioButton"))
    {
         //Is it selected?
         if (((RadioButton)c).Checked)
         {
             selected = c.Text;
 
             //De-select it now
             ((RadioButton)c).Checked = false;
         }
    }
}
 
Clipboard.SetText(selected);

发送符号 - 方法二

然而,每次都要自己粘贴符号会很麻烦。我知道这一点,因为我最初使用的是这款应用程序的一个远不如其完善的版本;一次偶然的机会,我决定重写它并在此 CodeProject 上发布。

因此,我在这个应用程序中也使用了 SendKeysSendKeys 可用于将字符串作为键盘输入发送到应用程序之间。例如:

SendKeys.Send("Hello World!");

在此示例中,当前具有焦点的应用程序将接收模拟键盘输入,就像有人刚刚键入“Hello World!”一样。模拟键盘输入的优点是可以是任何内容,甚至是符号。此方法实现起来稍微复杂一些——需要更多的外部调用——但结果使用起来要容易得多。

由于 SendKeys 将击键发送到当前活动的窗口,如果在我们的应用程序具有焦点时调用 SendKeys,符号将直接发送到我们的应用程序。

我使用了这些外部调用来解决这个问题。

[DllImport("user32.dll")]
private static extern IntPtr GetForegroundWindow();
 
[DllImport("user32.dll")]
private static extern int SetForegroundWindow(IntPtr hwnd);

当显示窗体时,应用程序会获取之前活动的应用程序,您可以在 ShowForm() 中看到这一点。

private void ShowForm()
{
    //Get the currently active window
    previousApp = GetForegroundWindow();
 
    this.Opacity = 1;
    this.Show();
 
    //Set as focused window
    SetForegroundWindow(this.Handle);

    //Ensure the cancel button is focussed.
    if (!btnCancel.Focused)
        btnCancel.Focus();
}

当用户单击“立即插入”按钮时,就会发生这种情况……

//Hide the form and reset the height
this.HideForm();
 
//Get the selected symbol
string selected = "";
foreach (Control c in this.Controls)
{
    if (c.ToString().Contains("System.Windows.Forms.RadioButton"))
    {
         //Is it selected?
         if (((RadioButton)c).Checked)
         {
             selected = c.Text;
 
             //De-select it now
             ((RadioButton)c).Checked = false;
         }
    }
}
 
//Set previous app as focused window and send the keys to it
try
{
    SetForegroundWindow(previousApp);
    SendKeys.Send(selected);
}
catch
{
    MessageBox.Show("The symbol could not be sent to your document.");
    this.ShowForm();
}

……您应该能够看到该过程是如何工作的。再次查看流程图应该有助于解释。

方法三 - 方法二的改进

我曾承诺您可以在手指不离开键盘的情况下插入符号。实现这一点将可以省去停止输入、使用鼠标/触摸板然后恢复输入的过程。

由于有十个可用的符号,我们可以使用数字键来选择所需的符号,其中 0 代表 10。

当此应用程序显示自身时,自动获得焦点的控件是“取消”按钮(请查看上面的 ShowForm() 代码)。我们所要做的就是为该控件添加一个 KeyUp 事件处理程序:

private void btnCancel_KeyUp(object sender, KeyEventArgs e)
{
    int j = 0;
    if (e.KeyData.ToString().Length == 2)
    {
        //Number keypresses are 'D1' to 'D0' respectively.
        if (int.TryParse(e.KeyData.ToString().Substring(1, 1), out j))
        {
            //If it was zero, send number 10.
            if (j == 0)
                j = 10;
 
            //Hide the form and reset the height
            this.HideForm();
 
            //Send the appropiate symbol
            try
            {
                SetForegroundWindow(previousApp);
                SendKeys.Send(Properties.Settings.Default.Symbols[j - 1].ToString());
            }
            catch
            {
                MessageBox.Show("The symbol could not be sent to your document.");
                this.ShowForm();
            }
        }
    }
}

瞧!这种最终方法提供了所有三种方法中最便捷的用户体验。

结论

希望您喜欢这篇文章,并且至少有一些人喜欢使用它。祝您打字愉快!如果您有任何评论或建议,请随时在下方发布。

历史

  • 2013/8/7:进一步优化文章。
  • 2012/9/3:对文章文本进行了一些优化。
  • 2012/4/8:发现在“编辑符号”对话框的任务栏中显示了其图标。已将其移除。
  • 2012/7/31:修复了取消按钮未获得焦点的问题。还修复了单选按钮未被清除的问题。
  • 2012/7/30:首次公开发布。
© . All rights reserved.