符号插入器






4.94/5 (23投票s)
一款应用程序,可让您在手指不离开键盘的情况下输入您最喜欢的十个符号。
引言
在我开始开发时,键盘上缺少几个符号(如 © 和 ☺)让我尤其恼火。这款应用程序是我最终为解决这个问题而创建的解决方案。
我写这篇文章时,先创建了一个可视化界面,然后将代码插入后台使其工作——因此我以这种格式呈现这篇文章。
应用程序的目标
该应用程序的基本目标如下:
- 尽量模仿操作系统外观和感觉。
- 实现键盘操作功能。这意味着在键盘上为应用程序注册一个热键,并使用键盘上的数字键来选择符号、关闭应用程序以及将符号插入您的文档、代码等。
- 允许用户自定义符号。
- 在用户登录时自动运行应用程序,这样就避免了每次都要启动它的麻烦。
- 在任务栏中保留一个通知图标,允许用户关闭应用程序。
这是应用程序单次使用的摘要。
创建可视化界面
命令链接和模仿操作系统外观和感觉
这些控件可以在 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;
然后您需要像下面这样重写 OnHandleCreated
、OnHandleDestroyed
和 WndProc
:
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 上发布。
因此,我在这个应用程序中也使用了 SendKeys
。SendKeys
可用于将字符串作为键盘输入发送到应用程序之间。例如:
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:首次公开发布。