本文探索的Demo地址:
https://gitee.com/lishuangquan1987/try_win32
https://github.com/lishuangquan1987/try_win32
后续会把他当做一个开源项目来维护
需求
开发一个软件,能够让用户说话来进行文字输入。具体如下:
- 像腾讯电脑管家那样的悬浮球悬浮在其他程序之上,支持拖动,点击开始录音,再点击结束录音。有录音提示、忙碌提示。
- 能够像输入法一样,对任何程序都具有语音输入功能,是一个通用的程序。
难点突破
- WPF 实现腾讯电脑管家悬浮球效果。
- WPF程序不抢占其他程序的焦点。
- WPF程序能够把语音翻译后的文字发送给具有焦点的程序。
- 语音转文字
突破过程
程序不具有焦点,且不抢占其他程序的焦点
winform的实现方式:
经过查资料,如下代码能达到要求:
public partial class Form2 : Form
{
public Form2()
{
InitializeComponent();
this.TopMost = true;
}
private const int WS_EX_TOOLWINDOW = 0x00000080;
private const int WS_EX_NOACTIVATE = 0x08000000;
private void button1_Click(object sender, EventArgs e)
{
this.textBox1.Text += "hello";//222211122
}
protected override CreateParams CreateParams
{
get
{
CreateParams cp = base.CreateParams;
cp.ExStyle |= (WS_EX_NOACTIVATE | WS_EX_TOOLWINDOW);
cp.Parent = IntPtr.Zero; // Keep this line only if you used UserControl
return cp;
}
}
}
实现效果如下:(一边移动窗体,一边按键盘输入,输入的还是记事本,可见Form2完全无焦点)
如上代码可以看到,是重写了CreateParams
属性,winform
可以重写,那wpf
呢?
在github上查看winform
源码,发现最终这个CreateParams
是调用win32的SetWindowLong
函数来进行设置。
翻看源码如下:
https://github.com/dotnet/winforms/blob/656dc8153a0bc464063ef9a4112f2eb94dcd100f/src/System.Windows.Forms/src/System/Windows/Forms/Form.cs#L758
/// <summary>
/// Retrieves the CreateParams used to create the window.
/// If a subclass overrides this function, it must call the base implementation.
/// </summary>
protected override CreateParams CreateParams
{
get
{
CreateParams cp = base.CreateParams;
if (IsHandleCreated && WindowStyle.HasFlag(WINDOW_STYLE.WS_DISABLED))
{
// Forms that are parent of a modal dialog must keep their WS_DISABLED style
cp.Style |= (int)WINDOW_STYLE.WS_DISABLED;
}
else if (TopLevel)
{
// It doesn't seem to make sense to allow a top-level form to be disabled
cp.Style &= ~(int)WINDOW_STYLE.WS_DISABLED;
}
if (TopLevel && (_formState[s_formStateLayered] != 0))
{
cp.ExStyle |= (int)WINDOW_EX_STYLE.WS_EX_LAYERED;
}
if (Properties.TryGetValue(s_propDialogOwner, out IWin32Window? dialogOwner))
{
cp.Parent = GetSafeHandle(dialogOwner).Handle;
}
FillInCreateParamsBorderStyles(cp);
FillInCreateParamsWindowState(cp);
FillInCreateParamsBorderIcons(cp);
if (_formState[s_formStateTaskBar] != 0)
{
cp.ExStyle |= (int)WINDOW_EX_STYLE.WS_EX_APPWINDOW;
}
FormBorderStyle borderStyle = FormBorderStyle;
if (!ShowIcon && (borderStyle is FormBorderStyle.Sizable or FormBorderStyle.Fixed3D or FormBorderStyle.FixedSingle))
{
cp.ExStyle |= (int)WINDOW_EX_STYLE.WS_EX_DLGMODALFRAME;
}
if (IsMdiChild)
{
if (Visible && (WindowState is FormWindowState.Maximized or FormWindowState.Normal))
{
Form? formMdiParent = Properties.GetValueOrDefault<Form>(s_propFormMdiParent);
Form? form = formMdiParent?.ActiveMdiChildInternal;
if (form is not null && form.WindowState == FormWindowState.Maximized)
{
cp.Style |= (int)WINDOW_STYLE.WS_MAXIMIZE;
_formState[s_formStateWindowState] = (int)FormWindowState.Maximized;
SetState(States.SizeLockedByOS, true);
}
}
if (_formState[s_formStateMdiChildMax] != 0)
{
cp.Style |= (int)WINDOW_STYLE.WS_MAXIMIZE;
}
cp.ExStyle |= (int)WINDOW_EX_STYLE.WS_EX_MDICHILD;
}
if (TopLevel || IsMdiChild)
{
FillInCreateParamsStartPosition(cp);
// Delay setting to visible until after the handle gets created
// to allow applyClientSize to adjust the size before displaying
// the form.
//
if ((cp.Style & (int)WINDOW_STYLE.WS_VISIBLE) != 0)
{
_formState[s_formStateShowWindowOnCreate] = 1;
cp.Style &= ~(int)WINDOW_STYLE.WS_VISIBLE;
}
else
{
_formState[s_formStateShowWindowOnCreate] = 0;
}
}
if (RightToLeft == RightToLeft.Yes && RightToLeftLayout)
{
// We want to turn on mirroring for Form explicitly.
cp.ExStyle |= (int)(WINDOW_EX_STYLE.WS_EX_LAYOUTRTL | WINDOW_EX_STYLE.WS_EX_NOINHERITLAYOUT);
// Don't need these styles when mirroring is turned on.
cp.ExStyle &= ~(int)(WINDOW_EX_STYLE.WS_EX_RTLREADING | WINDOW_EX_STYLE.WS_EX_RIGHT | WINDOW_EX_STYLE.WS_EX_LEFTSCROLLBAR);
}
return cp;
}
}
从这个源码可以知道,我们设置Form的FormBorderStyle
/WindowState
/TopLevel
/IsMdiChild
/RightToLeft
等属性后,CreateParams
都会改变。
Form的继承关系如下:Control
->ScrollableControl
->ContainerControl
->Form
.
CreateParams
就定义在Control
类中,是一个虚方法,子类可以重写。
继续寻找源码,看到这里使用了CreateParams
:
https://github.com/dotnet/winforms/blob/656dc8153a0bc464063ef9a4112f2eb94dcd100f/src/System.Windows.Forms/src/System/Windows/Forms/Form.cs#L1634:
/// <summary>
/// Determines the opacity of the form. This can only be set on top level controls.
/// Opacity requires Windows 2000 or later, and is ignored on earlier operating systems.
/// </summary>
[SRCategory(nameof(SR.CatWindowStyle))]
[TypeConverter(typeof(OpacityConverter))]
[SRDescription(nameof(SR.FormOpacityDescr))]
[DefaultValue(1.0)]
public double Opacity
{
get => Properties.GetValueOrDefault(s_propOpacity, 1.0d);
set
{
value = Math.Clamp(value, 0.0d, 1.0d);
Properties.AddOrRemoveValue(s_propOpacity, value, defaultValue: 1.0d);
bool oldLayered = _formState[s_formStateLayered] != 0;
if (OpacityAsByte < 255)
{
AllowTransparency = true;
if (_formState[s_formStateLayered] != 1)
{
_formState[s_formStateLayered] = 1;
if (!oldLayered)
{
UpdateStyles();
}
}
}
else
{
_formState[s_formStateLayered] = (TransparencyKey != Color.Empty) ? 1 : 0;
if (oldLayered != (_formState[s_formStateLayered] != 0))
{
CreateParams cp = CreateParams;
if ((int)ExtendedWindowStyle != cp.ExStyle)
{
PInvokeCore.SetWindowLong(this, WINDOW_LONG_PTR_INDEX.GWL_EXSTYLE, cp.ExStyle);
}
}
}
UpdateLayered();
}
}
最后使用了PInvokeCore.SetWindowLong(this, WINDOW_LONG_PTR_INDEX.GWL_EXSTYLE, cp.ExStyle);
cp就是我们的CreateParams
参数。user32.dll中也有SetWindowLong
函数,于是我们做一下尝试,在wpf中也像这样调用:
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
const int WS_EX_TOOLWINDOW = 0x00000080;
const int WS_EX_NOACTIVATE = 0x08000000;
const int GWL_EXSTYLE = -20;
public MainWindow()
{
InitializeComponent();
this.Loaded += MainWindow_Loaded;
}
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
WindowInteropHelper helper = new WindowInteropHelper(this);
int GWL_EXSTYLE = -20;
var style = User32Helper.GetWindowLong(helper.Handle, GWL_EXSTYLE);
//cp.ExStyle |= (WS_EX_NOACTIVATE | WS_EX_TOOLWINDOW);
//cp.Parent = IntPtr.Zero; // Keep this line only if you used UserControl
style |= (WS_EX_NOACTIVATE | WS_EX_TOOLWINDOW);
User32Helper.SetWindowLong(helper.Handle, GWL_EXSTYLE, new IntPtr(style));
}
}
最后发现OK啦。
向其他具有焦点的程序发送文本
-
获取具有焦点的活动窗口
/// <summary> /// 获取当前活动窗口 /// </summary> /// <returns></returns> [DllImport("user32.dll")] public static extern IntPtr GetForegroundWindow();
-
向指定句柄的窗口发送文本:
查了好多资料,都说调用user32.dll的SendMessage
函数,但是发现没有达到想要的效果。
最后发现使用winform包下的SendKeys.SendWait
函数可以满足需求,暂时没有深挖SendKeys.SendWait
背后到底是调用了哪些user32.dll函数。
将.net8的wpf
程序引用winform
包:<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>WinExe</OutputType> <TargetFramework>net8.0-windows</TargetFramework> <Nullable>enable</Nullable> <ImplicitUsings>enable</ImplicitUsings> <UseWPF>true</UseWPF> <UseWindowsForms>true</UseWindowsForms> </PropertyGroup> <ItemGroup> <ProjectReference Include="..\Common\Common.csproj" /> </ItemGroup> </Project>
需要添加
<UseWindowsForms>true</UseWindowsForms>
代码如下:try { var handle = User32Helper.GetForegroundWindow(); string text = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); #region 不起作用 //User32Helper.COPYDATASTRUCT cds; //cds.dwData= (IntPtr)0; //cds.cbData = text.Length + 1; //cds.lpData = text; //var result= User32Helper.SendMessage(handle, WM_SETTEXT, IntPtr.Zero, ref cds); //Marshal.FreeHGlobal(buffer); #endregion SendKeys.SendWait(text); } catch (Exception ee) { System.Windows.MessageBox.Show(ee.Message); }
最后实现的效果如下:
可以看到中间发送的时间好像有乱码一样,是因为我把输入法换成了中文了,所以成这样了。
我尝试过,发送中文也是没有任何问题的。解决了以上问题,离语音转文字输入法的想法又更近一步啦。
WPF实现悬浮球效果
要求如下:
1.窗口置于最顶端
2.无边框圆形窗体
3.能够拖动
4.能够响应鼠标点击
5.不抢占焦点
经过几天的折腾,最后实现如下:
MainWindow.xaml
<Window
x:Class="Test.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:converters="clr-namespace:YOFC.SpeechInput.Converters"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
xmlns:local="clr-namespace:Test"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="MainWindow"
Width="100"
Height="100"
d:DataContext="{d:DesignInstance local:MainWindowViewModel}"
AllowsTransparency="True"
Background="Transparent"
ResizeMode="NoResize"
ShowInTaskbar="False"
Topmost="True"
WindowStyle="None"
mc:Ignorable="d">
<Window.Resources>
<Style x:Key="FocusVisual">
<Setter Property="Control.Template">
<Setter.Value>
<ControlTemplate>
<Rectangle
Margin="2"
SnapsToDevicePixels="true"
Stroke="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"
StrokeDashArray="1 2"
StrokeThickness="1" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<SolidColorBrush x:Key="Button.Static.Background" Color="#FFDDDDDD" />
<SolidColorBrush x:Key="Button.Static.Border" Color="#FF707070" />
<SolidColorBrush x:Key="Button.MouseOver.Background" Color="#FFBEE6FD" />
<SolidColorBrush x:Key="Button.MouseOver.Border" Color="#FF3C7FB1" />
<SolidColorBrush x:Key="Button.Pressed.Background" Color="#FFC4E5F6" />
<SolidColorBrush x:Key="Button.Pressed.Border" Color="#FF2C628B" />
<SolidColorBrush x:Key="Button.Disabled.Background" Color="#FFF4F4F4" />
<SolidColorBrush x:Key="Button.Disabled.Border" Color="#FFADB2B5" />
<SolidColorBrush x:Key="Button.Disabled.Foreground" Color="#FF838383" />
<Style x:Key="CircleButton" TargetType="{x:Type Button}">
<Setter Property="FocusVisualStyle" Value="{StaticResource FocusVisual}" />
<Setter Property="Background" Value="{StaticResource Button.Static.Background}" />
<Setter Property="BorderBrush" Value="{StaticResource Button.Static.Border}" />
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="Padding" Value="1" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Grid>
<Ellipse Fill="{TemplateBinding Background}" />
<ContentPresenter
x:Name="contentPresenter"
Margin="{TemplateBinding Padding}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
Focusable="False"
RecognizesAccessKey="True"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<SolidColorBrush x:Key="FlashColor">LightBlue</SolidColorBrush>
<converters:BooleanToInVisibilityConverter x:Key="BooleanToInVisibilityConverter" />
<converters:BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
</Window.Resources>
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<i:InvokeCommandAction Command="{Binding LoadedCmd}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<Grid Background="Transparent">
<i:Interaction.Triggers>
<i:EventTrigger EventName="PreviewMouseLeftButtonDown">
<i:InvokeCommandAction Command="{Binding MouseLeftButtonDownCmd}" PassEventArgsToCommand="True" />
</i:EventTrigger>
<i:EventTrigger EventName="PreviewMouseLeftButtonUp">
<i:InvokeCommandAction Command="{Binding MouseLeftButtonUpCmd}" PassEventArgsToCommand="True" />
</i:EventTrigger>
<i:EventTrigger EventName="PreviewMouseMove">
<i:InvokeCommandAction Command="{Binding MouseMoveCmd}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<Grid Visibility="{Binding IsRecording, Converter={StaticResource BooleanToVisibilityConverter}}">
<Grid.Triggers>
<EventTrigger RoutedEvent="Loaded">
<BeginStoryboard>
<Storyboard
AutoReverse="True"
FillBehavior="HoldEnd"
RepeatBehavior="Forever"
Duration="0:0:0.5">
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="ellipse1_0"
Storyboard.TargetProperty="Height"
From="60"
To="100" />
<DoubleAnimation
Storyboard.TargetName="ellipse1_0"
Storyboard.TargetProperty="Width"
From="60"
To="100" />
</Storyboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="ellipse0_8"
Storyboard.TargetProperty="Height"
From="80"
To="100" />
<DoubleAnimation
Storyboard.TargetName="ellipse0_8"
Storyboard.TargetProperty="Width"
From="80"
To="100" />
</Storyboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="ellipse0_4"
Storyboard.TargetProperty="Height"
From="40"
To="100" />
<DoubleAnimation
Storyboard.TargetName="ellipse0_4"
Storyboard.TargetProperty="Width"
From="40"
To="100" />
</Storyboard>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Grid.Triggers>
<Ellipse
Name="ellipse1_0"
Fill="{StaticResource FlashColor}"
Opacity="1" />
<Ellipse
Name="ellipse0_8"
Fill="{StaticResource FlashColor}"
Opacity="0.8" />
<Ellipse
Name="ellipse0_4"
Fill="{StaticResource FlashColor}"
Opacity="0.4" />
</Grid>
<Button
Width="100"
Height="100"
Padding="0"
Background="LightBlue"
Command="{Binding StartOrStopRecordCmd}"
Opacity="0.8"
Style="{DynamicResource CircleButton}">
<Grid Visibility="{Binding IsConnected, Converter={StaticResource BooleanToVisibilityConverter}}">
<Viewbox Width="50" Height="50">
<Path
Data="M661.9136 329.1136c12.1856 0 18.3296 6.144 18.3296 18.3296l0 182.8864c0 62.5664-20.992 116.224-62.8736 161.1776C575.488 736.3584 523.264 761.9584 460.8 768l0 219.4432 91.4432 0c12.1856 0 18.3296 6.144 18.3296 18.3296 0 12.288-6.144 18.3296-18.3296 18.3296L332.8 1024.1024c-12.1856 0-18.3296-6.0416-18.3296-18.3296 0-12.1856 6.144-18.3296 18.3296-18.3296l91.4432 0L424.2432 768c-62.464-6.0416-114.688-31.6416-156.5696-76.5952C225.792 646.5536 204.8 592.7936 204.8 530.3296L204.8 347.4432c0-12.1856 6.144-18.3296 18.3296-18.3296s18.3296 6.144 18.3296 18.3296l0 182.8864c0 56.4224 19.456 104.0384 58.2656 142.848 38.8096 38.8096 86.4256 58.2656 142.848 58.2656 56.4224 0 104.0384-19.456 142.848-58.2656 38.912-38.8096 58.2656-86.4256 58.2656-142.848L643.6864 347.4432C643.6864 335.2576 649.728 329.1136 661.9136 329.1136zM325.9392 646.8608c31.9488 31.9488 70.8608 48.0256 116.5312 48.0256 45.6704 0 84.5824-15.9744 116.5312-48.0256s48.0256-70.8608 48.0256-116.5312L607.0272 164.5568c0-45.6704-15.9744-84.5824-48.0256-116.5312C527.0528 15.9744 488.2432 0 442.4704 0 396.8 0 357.9904 15.9744 325.9392 48.0256 293.9904 79.9744 277.9136 118.8864 277.9136 164.5568l0 365.6704C277.9136 576 293.9904 614.8096 325.9392 646.8608zM352.256 74.24C377.344 49.152 407.4496 36.5568 442.4704 36.5568S507.6992 49.152 532.7872 74.24c25.1904 25.1904 37.6832 55.1936 37.6832 90.3168l0 365.6704c0 35.1232-12.5952 65.1264-37.6832 90.3168C507.6992 645.7344 477.5936 658.3296 442.4704 658.3296S377.344 645.7344 352.256 620.544C327.0656 595.456 314.4704 565.3504 314.4704 530.3296L314.4704 164.5568C314.4704 129.536 327.0656 99.4304 352.256 74.24z"
Fill="#1296db"
Stretch="Uniform" />
</Viewbox>
</Grid>
</Button>
<Grid Visibility="{Binding IsConnected, Converter={StaticResource BooleanToInVisibilityConverter}}">
<Ellipse Fill="LightBlue" />
<Viewbox
Width="50"
Height="50"
Visibility="Visible">
<Path
Data="M316.8 557.6a15.632 15.632 0 0 0-9.6 4.208c-2.72 2.864-3.328 7.184-3.184 9.456 6.576 102.16 86.352 184.32 187.184 193.952v53.216a16 16 0 0 0 16 16h16a16 16 0 0 0 16-16v-52.944c20.72-1.664 40.864-6.4 59.936-14.016a12.912 12.912 0 0 0 6.624-6.944c1.344-3.472 1.072-8.08 0.176-9.952l-9.952-20.864c-0.848-1.76-3.392-5.264-7.2-6.464-3.472-1.12-8.16-0.064-10.112 0.688a158.144 158.144 0 0 1-56.144 10.224h-11.456c-84 0-152.88-65.392-159.04-148.432a16.96 16.96 0 0 0-3.872-8.4 12.128 12.128 0 0 0-8.224-3.744z m40.64-350.56a16 16 0 0 0-22.24-4.192l-13.216 9.024a16 16 0 0 0-4.192 22.24l366.88 536.928a16 16 0 0 0 22.24 4.192l13.216-9.024a16 16 0 0 0 4.176-22.24z m47.36 193.216a168.64 168.64 0 0 0-21.6 0c-10.4 1.072-13.6 8.176-13.6 12.272v123.84a147.872 147.872 0 0 0 192.688 140.96c4.112-0.976 8.112-7.2 4.56-15.2l-8.256-21.008c-1.792-7.792-9.792-10.672-14.96-8.32a99.872 99.872 0 0 1-126.032-96.432v-123.84c0-8-5.712-11.2-12.8-12.272z m289.344 157.328c-2.256 0.016-6.032 0.208-8.544 2.144-3.088 2.368-4.416 7.184-4.832 9.536-2.032 11.408-6.4 26.752-12.272 43.36-0.688 1.984-1.888 6.896-0.496 10.304 1.424 3.44 5.456 5.376 7.36 6.08l21.456 8.176c2.08 0.8 7.552 2.784 11.344 0.944 2.976-1.456 4.544-6.48 5.152-8.192l0.416-1.2c8.08-22.8 13.76-43.584 15.632-60.192a10.4 10.4 0 0 0-3.76-8.176c-2.56-2.08-6.72-2.784-8.8-2.784zM517.472 192a147.36 147.36 0 0 0-90.88 31.2c-7.392 4.848-8.992 14.928-1.984 21.856l12.112 12.368c6.48 6.304 16.08 4.704 22.656 1.2a99.872 99.872 0 0 1 157.984 81.264v196.48c0 5.44 1.84 13.76 12.784 15.328l20 2.688c9.056 0.544 14.864-6.016 15.056-11.2l0.16-4.48V339.856A147.872 147.872 0 0 0 517.472 192z"
Fill="Red"
Stretch="Uniform" />
</Viewbox>
</Grid>
</Grid>
</Window>
MainWindowViewModel.cs
public partial class MainWindowViewModel : ObservableObject
{
const int WS_EX_TOOLWINDOW = 0x00000080;
const int WS_EX_NOACTIVATE = 0x08000000;
const int GWL_EXSTYLE = -20;
private DateTime _lastPressedTime;
private bool _isPressed = false;
private POINT _lastPoint;
public MainWindowViewModel()
{
StartOrStopRecordCmd = new RelayCommand(StartOrStopRecord, () => this.IsConnected);
this.LoadedCmd = new RelayCommand(Loaded);
this.MouseLeftButtonDownCmd = new RelayCommand<MouseButtonEventArgs>(MouseLeftButtonDown);
this.MouseLeftButtonUpCmd = new RelayCommand<MouseButtonEventArgs>(MouseLeftButtonUp);
this.MouseMoveCmd = new RelayCommand(MouseMove);
}
private void MouseMove()
{
if (_isPressed)
{
User32Helper.GetCursorPos(out var point);
App.Current.MainWindow.Left = App.Current.MainWindow.Left + point.X - _lastPoint.X;
App.Current.MainWindow.Top = App.Current.MainWindow.Top + point.Y - _lastPoint.Y;
//赋值
_lastPoint = point;
}
}
private void MouseLeftButtonUp(MouseButtonEventArgs? e)
{
_isPressed = false;
//长按的话,不响应Button点击事件
if ((DateTime.Now - _lastPressedTime).TotalSeconds > 0.5)
{
e.Handled = true;
}
}
private void MouseLeftButtonDown(MouseButtonEventArgs? e)
{
_isPressed = true;
_lastPressedTime = DateTime.Now;
User32Helper.GetCursorPos(out _lastPoint);
}
private void Loaded()
{
var helper = new WindowInteropHelper(App.Current.MainWindow);
int GWL_EXSTYLE = -20;
var style = User32Helper.GetWindowLong(helper.Handle, GWL_EXSTYLE);
style |= (WS_EX_NOACTIVATE | WS_EX_TOOLWINDOW);
User32Helper.SetWindowLong(helper.Handle, GWL_EXSTYLE, new IntPtr(style));
App.Current.MainWindow.Left = SystemParameters.PrimaryScreenWidth - App.Current.MainWindow.Width;
App.Current.MainWindow.Top = SystemParameters.PrimaryScreenHeight / 2;
}
private async void StartOrStopRecord()
{
if (!IsConnected)
{
MessageBox.Show("未连接到服务器", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
return;
}
if (IsRecording)
{
await speechInput.Stop();
IsRecording = false;
}
else
{
await speechInput.Start();
IsRecording = true;
}
}
~MainWindowViewModel()
{
speechInput.Unload();
}
/// <summary>
/// 是否正在录音
/// </summary>
[ObservableProperty] private bool _isRecording;
/// <summary>
/// 是否连接上了服务端
/// </summary>
[NotifyCanExecuteChangedFor(nameof(StartOrStopRecordCmd))]
[ObservableProperty] private bool _isConnected;
public RelayCommand StartOrStopRecordCmd { get; }
public RelayCommand<MouseButtonEventArgs> MouseLeftButtonDownCmd { get; }
public RelayCommand<MouseButtonEventArgs> MouseLeftButtonUpCmd { get; }
public RelayCommand MouseMoveCmd { get; }
public RelayCommand LoadedCmd { get; }
}
最后实现效果: