当用户点击控制台窗口时,代码停止执行。

28
我有一个控制台应用程序,可以在没有用户交互的情况下执行我的代码。如果用户在控制台窗口内点击,无论是故意还是意外,所有执行都会停止。
这与从控制台窗口复制文本有关。唯一的方法是,如果用户选择文本,然后右键单击控制台窗口,将其复制到剪贴板中,应用程序才能重新开始执行。
要查看此操作,请创建控制台应用程序并添加以下代码。
class Program
{
    static void Main(string[] args)
    {
        var task = Task.Run(async () =>
        {
            int i = 0;
            while (true)
            {
                Console.WriteLine(i++);
                await Task.Delay(1000);
            }
        });
        Console.ReadLine();
    }
}

当您单击控制台窗口时,任务线程停止执行。这种行为不可取,我想防止在我的控制台应用程序中发生这种情况。
我该如何防止这种情况发生?就我所见,控制台窗口上的所有属性/事件都与控制此行为无关。
正如您所看到的,当我单击窗口时,会出现光标。当我按任意键时-光标消失,应用程序继续工作。 Paused app

3
请停止编辑此内容,我已经重写了一遍,希望能够澄清正在发生的事情。@ShannonHolsinger 的问题在于你无法确切地告诉用户不要意外点击控制台窗口... - user1228
3
我不确定,但这可能是在Windows 10中新增的功能。如果您在客户机上运行了一个长时间运行的控制台应用程序,并且当客户试图将窗口置于焦点时意外停止执行它,那么这种行为非常不可取。此外,没有任何指示表明发生了什么,也没有说明如何重新运行该应用程序。 - user1228
1
请看这个链接,虽然不完全一样,但包含了有用的提示。 - Sinatr
2
@Mahdi,你用的是什么操作系统?我认为这个改变是针对Windows 10的新特性。 - user1228
2
@Mahdi 我也是,我可以重现这个问题。 - user1228
显示剩余10条评论
2个回答

39

如果您在控制台窗口启用了“快速编辑模式”,就会出现这种情况。如果您右键单击标题栏并选择“属性”,然后选择“选项”标签,您可以检查是否启用了“快速编辑模式”。如果禁用“快速编辑模式”,则在单击窗口时滚动不会停止。

滚动停止的原因是因为在窗口中单击鼠标会用于选择文本。

您可以在程序中禁用控制台上的“快速编辑模式”,但这样做需要调用GetConsoleModeSetConsoleMode API函数。以下是如何执行此操作:

[DllImport("kernel32.dll", SetLastError=true)]
public static extern IntPtr GetConsoleWindow();

[DllImport("kernel32.dll", SetLastError=true)]
public static extern bool GetConsoleMode(
    IntPtr hConsoleHandle,
    out int lpMode);

[DllImport("kernel32.dll", SetLastError=true)]
public static extern bool SetConsoleMode(
    IntPtr hConsoleHandle,
    int ioMode);

/// <summary>
/// This flag enables the user to use the mouse to select and edit text. To enable
/// this option, you must also set the ExtendedFlags flag.
/// </summary>
const int QuickEditMode = 64;

// ExtendedFlags must be combined with
// InsertMode and QuickEditMode when setting
/// <summary>
/// ExtendedFlags must be enabled in order to enable InsertMode or QuickEditMode.
/// </summary>
const int ExtendedFlags = 128;

void DisableQuickEdit()
{
    IntPtr conHandle = GetConsoleWindow();
    int mode;

    if (!GetConsoleMode(conHandle, out mode))
    {
        // error getting the console mode. Exit.
        return;
    }

    mode = mode & ~(QuickEditMode | ExtendedFlags);

    if (!SetConsoleMode(conHandle, mode))
    {
        // error setting console mode.
    }
}

void EnableQuickEdit()
{
    IntPtr conHandle = GetConsoleWindow();
    int mode;

    if (!GetConsoleMode(conHandle, out mode))
    {
        // error getting the console mode. Exit.
        return;
    }

    mode = mode | (QuickEditMode | ExtendedFlags);

    if (!SetConsoleMode(conHandle, mode))
    {
        // error setting console mode.
    }
}

如果您选择这条路线,最好在程序启动时保存原始控制台模式设置,并在程序退出时恢复它。因此,在启动时:

GetConsoleMode(GetConsoleWindow(), ref saveConsoleMode);

当您的程序终止时:

SetConsoleMode(GetConsoleWindow(), saveConsoleMode);

当然,要进行适当的错误处理。如果调用GetConsoleMode失败,您不希望还原控制台模式。


这似乎只在从Visual Studio的调试中运行时有效。当单独启动程序时,即使以管理员身份运行,问题仍然存在。有任何想法吗? - tayoung
1
@tayoung:这对我有效,即使独立运行也是如此。如果您有反例,请发布一个带有示例的问题。 - Jim Mischel
@jimMischel 我遇到了类似的问题。我检查了你的两个解决方案(这一个和你之前发布的那个)。我在快速编辑模式中打开和关闭都试过了。(结果都是一样的)。我还没有找到解决办法。有没有什么方法可以解决这个错误? - Nikunj Chaklasiya
有人能解释一下 mode = mode & ~(QuickEditMode | ExtendedFlags); 这段代码的含义吗? - bzmind
1
@SMH 这是一个位掩码,用于关闭“QuickEditMode”和“ExtendedFlags”。假设“QuickEditMode = 1”和“ExtendedFlags = 2”。这些不是实际值,但它们可以使用。因此,“(QuickEditMode | ExtendedFlags)= 1 | 2”。那是“3”,或二进制的“00000011”。 “~”(非)运算符翻转位,因此变为二进制的“11111100”。现在,假设“mode”是“01011011”。如果我将其与“11111100”进行“&”(与)操作,则会得到“011011000”:它清除了“mode”值中的“QuickEditMode”和“ExtendedFlags”位。有关更多信息,请参见https://www.tutorialspoint.com/csharp/csharp_bitwise_operators.htm。 - Jim Mischel
显示剩余2条评论

15

我刚刚看到在OP的问题评论中链接的这个答案包含了我自己找到的内容。我将保留我的答案,因为可能有人没有看到它,就像我一样,这将节省他们很多时间。


Jim的答案对我没有用,我弄不清为什么。 我四处搜索并找到了一个可行的解决方案,所以我将分享我的发现,希望能帮助处于同样情况的人。

问题出在我从GetConsoleWindow()中获取的句柄上,当我尝试使用它时,它会给出Win32错误(0x6),即句柄无效。调用SetConsoleMode()也没有任何作用。

为了获得一个可用的句柄,我使用GetStdHandle()来获取控制台输入句柄。将此添加到Jim的代码中:

public const int STD_INPUT_HANDLE = -10;

[DllImport("Kernel32.dll", SetLastError = true)]
public static extern IntPtr GetStdHandle(int nStdHandle);

然后在Jim的代码中,在DisableQuickEdit()EnableQuickEdit()中用GetStdHandle(STD_INPUT_HANDLE)替换GetConsoleWindow()

调用DisableQuickEdit()后,控制台中的选择被禁用。

谢谢Jim!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接