在WPF中无操作后返回

在很多时候,我们需要使用WPF来实现一些程序,例如展示程序。最近我就做了这样的程序。但是有一个问题,就是如果做展示程序(类似触摸屏的那种),那么有必要让程序在无操作后返回到一个类似屏保的界面(可以参考windows的屏幕保护或者自动锁定)。很遗憾,我没有在wpf中发现具有这样功能的类/函数,只能转向求助于windows API。

在windows api中(“user32.dll”),有一个名为“ GetLastInputInfo”的API,在MSDN上,它的解释是这样的:

This fundtion is useful for input idle detection. However, GetLastInputInfo does not provide system-wide user input information across all running sessions. Rather, GetLastInputInfo provides session-specific user input information for only the session thar invoked the function.

The tick count when the last input event was received (see LastInputInfo) is not guaranteed to be incremental. In some cases, the value might be less than the tick count of a prior event. For example, this can be caused by a timing gap between the raw input thread and the desktop thread or an event raised by SendInput, which supplies its own tick count.

上面第一段话的意思是,GetLastInputInfo并不提供全局(在整个桌面环境中)的输入信息,而是提供特定的会话的输入信息。

第二段的意思是,接收到的输入时间的tick count并不是完全准确的,在某些情况下,可能会比实际的值要小,这种情况可能会由纯输入线程和桌面线程之间的时间差,或者是由SendInput引起。

_注:由于个人水平有限,以上引语的翻译可能不准确,所以最好阅读原文。另外,以上内容不会随着其出处更新而更新,用时请参考其出处_

所以最终我选用这个GetLastInputInfo,在C#/WPF中写好引用即可。
下面是本功能的核心代码

using System.Runtime.InteropServices; // 如果需要调用外部的dll的话,这个引用是必要的
using System.Windows;
using System.Windows.Threading;  // 此处是为了使用WPF的DispatcherTimer,
                                                                 // 如果你需要使用其他的Timer,请自行using
namespace
{
    public class TestClass
    {
        private DispatcherTimer timer; // 我们需要使用的timer,在timer里面我们要检测上一次操作的信息
        private readonly static int defultIdleAllowed = 5; // 默认的允许idle时间(单位为s),在这个时间里,如果没有操作,那么就返回,或者执行其他的操作
        private static int currentIdleTimeRemained = defaultIdleAllowed; // 当前剩余的可以idle的时间,单位为s

        public TestClass()
        {
            timer = new DispatcherTimer();
            timer.Tick += new EventHandler(Timer_Tick);
            timer.Interval = new TimeSpan(0, 0, 1);
            timer.Start();
        }

        private void Timer_Tick(object sender, EventArgs e)
        {
            if(HasActionInLastSecond())
            {
                if(--currentIdleTimeRemained == 0)
                {
                    currentIdleTime = defaultIdleAllowed;
                    timer.Stop();

                    // 在这里你可以做一些类似返回主屏幕的操作

                    timer.Start();
                }
            }
            else
            {
                currentIdleTime = defaultIdleAllowed;
            }
        }

        /// <summary>
        /// 未操作时间超过1s才开始计数
        /// </summary>
        /// <returns>bool,表示未操作时间是否超过1s</returns>
        private bool HasActionInLastSecond()
        {
            return GetIdleTime() / 1000 > 1;
        }

        /// <summary>
        /// 通过window api获得上次键鼠操作的时间,返回值为ms
        /// </summary>
        /// <param name = "pLastInputInfo">ref, 存放时间的一个结构体,在下文中会有定义</param>
        /// <returns></returns>
        [DllImport("user32.dll")]
        static extern bool GetLastInputInfo(ref PLASTINPUTINFO pLastInputInfo);

        private static long GetIdleTime()
        {
            PLASTINPUTINFO pLastInputInfo = new PLASTINPUTINFO;
            pLastInputInfo.cbSize = Marshal.SizeOf(pLastInputInfo);
            if(!GetLastInputInfo(ref pLastInputInfo))
            {
                return 0;
            }
            return Environment.TickCount- pLastInputInfo.dwTime;
        }

        // PLASTINPUTINFO结构体定义
        [StructLayout(LayoutKind.Sequential)]
        private struct PLASTINPUTINFO
        {
            [MarshalAs(UnmanagedType.U4)]
            public int cbSize;
            [MarshalAs(UnmanagedType.U4)]
            public uint dwTime;
        }

C#下使程序进入全屏的方法

在C#下可以使用两种方法使程序进入全屏状态,一是使用内置的方法,另一种是利用WinAPI
1. 使用WinAPI
这种方法的原理是利用WinAPI获取屏幕的显示范围,并隐藏任务栏,再在窗体中使Form Maxmize以及borderless实现的,其中隐藏任务栏的操作比较复杂,需要使用WinAPI才可以实现(如果其他的方法也可以实现,欢迎评论)
source code:

using System.Runtime.InteropService; //此using的用途在于加载user32.dll
//其他的using略去
#region user32.dll
[DllImport("user32.dll", EntryPoint = "ShowWindow")]
public static extern Int32 ShowWindow(Int32, hwnd, Int32, nCmdShow);
public const Int32 SW_SHOW = 5;
public const Int32 SW_HIDE = 0;

[DllImport("user32.dll", EntryPoint = "SystemParametersInfo")]
private static extern Int32 SystemParametersInfo(Int32 uAction, Int32 uParm, ref Rectangle lpvParam, Int32 fuWinIni);
public const Int32 SPIF_UPDATEINIFILE = 0x1;
public const Int32 SPI_SETWORKAREA = 47;
public const Int32 SPI_GETWORKAREA = 48;

[DllImport("user32.dll", EntryPoint = "FindWindow")]
private static extern Int32 FindWindow(string lpClassName, string lpWindowName);

public Boolean SetFullScreen(Boolean isFullScreen)
{
    Rectangle rectLast = Rectangle.Empty;
    Int32 hwnd = 0;
    hwnd = FindWindow("Shell_TrayWnd", null); //此处是获得任务栏的句柄

    if( 0 == hwnd) return false;
    if( isFullScreen )
    {
        ShowWindow(hwnd, SW_HIDE); //隐藏任务栏
        SystemParametersInfo(SPI_GETWORKAREA, 0, ref rectLast, SPIF_UPDATEINIFILE);//获得屏幕范围
        Rectangle rectFull = Screen.PrimaryScreen.Bounds; // 获取第一屏幕的显式范围
        SystemParametersInfo(SPI_SETWORKAREA, 0, ref rectFull, SPI_UPDATEINIFILE);
    }
    else //还原窗口
    {
        ShowWindow(hwnd, SW_SHOW);
        SystemParametersInfo(SPI_SETWORKAREA, 0, ref rectLast, SPIF_UPDATEINIFILE);//还原窗口
    }
    return true;

  1. 使用内置的方法 强烈推荐
    从上面的代码我们可以看出,利用WinAPI虽然比较自由,比较强大,但是也过于繁琐,对于一个简单的引用来说也没有必要。所以在此pret强烈推荐使用内置的方法来实现

source code(partial)

public void GoFullScreen()
{
    this.FormBorderStyle = FormBorderStyle.None; // 设置无边框
    this.WindowState = FormWindowState.Maximized;//进入全屏
    this.TopMost = true; // 在最上面显示
}

以上是必要的代码,如果需要,还可以设置
– ResizeMode为WindowStyle.NoResize
同时如果需要把窗体恢复,那么在设置全屏之前应该把窗体的一些信息例如:
– WindowState
– WindowStyle
– ResizeMode
– TopMost
– Left
– Top
– Width
– Height
等等保存起来,再执行全屏,等到需要恢复的时候再从刚刚保存的恢复即可。