在媒体播放期间从C#中的WPF应用程序启用Windows上的屏幕保护程序

时间:2017-11-01 20:31:06

标签: c# wpf windows mediaelement screensaver

我是WPF和c#的新手。我在一个简单的WPF应用程序中使用MediaElement来播放循环视频,使用故事板。

现在在这个项目中,我遇到了一个大问题。 MediaElement中播放视频会禁止屏幕保护程序启动。但就我而言,我需要正常的Windows行为。 (自动屏幕保护程序和自动注销等。)

即使我的MediaElement中正在播放视频,如何让普通的屏幕保护程序再次显示?

守则很简单: 主窗口:

<Window x:Name="BrutusView" x:Class="BRUTUS_Panel_View.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:BRUTUS_Panel_View"
mc:Ignorable="d"
        Title="3nb BRUTUS Side Panel Player" Height="320" Width="256" HorizontalAlignment="Center" VerticalAlignment="Center" Left="0" Top="0" Deactivated="BrutusView_Deactivated" LostFocus="BrutusView_LostFocus" Loaded="BrutusView_Loaded" WindowStyle="ToolWindow" ResizeMode="CanResize" Icon="F:\Dokumente\Visual Studio 2015\Projects\BRUTUSConfig\BRUTUSConfig\3nb.ico" Topmost="True" ShowInTaskbar="False" ShowActivated="False" Visibility="Visible">
    <Grid>
        <MediaElement x:Name="myMediaElement" LoadedBehavior="Manual" HorizontalAlignment="Center"  VerticalAlignment="Center" IsMuted="True" Stretch="UniformToFill">
            <MediaElement.Triggers>
                <EventTrigger RoutedEvent="MediaElement.Loaded">
                    <EventTrigger.Actions>
                        <BeginStoryboard>
                            <Storyboard>
                                <MediaTimeline Source="C:\Animations\1.mp4" Storyboard.TargetName="myMediaElement" RepeatBehavior="Forever" />
                            </Storyboard>
                        </BeginStoryboard>
                    </EventTrigger.Actions>
                </EventTrigger>
            </MediaElement.Triggers>
        </MediaElement>
    </Grid>
</Window>

c#:

using System;
using System.Linq;
using System.Windows;
using System.Windows.Forms;
using System.Windows.Threading;

namespace BRUTUS_Panel_View
{

    public partial class MainWindow : Window
    {



        public MainWindow()
        {
            InitializeComponent();
        }

        private void BrutusView_Loaded(object sender, RoutedEventArgs e)
        {

        }

        private void BrutusView_Deactivated(object sender, EventArgs e)
        {
            BrutusView.Topmost = true;
        }

        private void BrutusView_LostFocus(object sender, RoutedEventArgs e)
        {
            BrutusView.Topmost = true;
        }

    }
}

1 个答案:

答案 0 :(得分:2)

答案包含两个解决方案:(i)基于UWP DisplayRequest类,(ii)基于自定义ScreenSaverManager类更为通用。

<强> 1。 UWP DisplayRequest

Windows提供了直接请求暂停屏幕保护程序的方法,并成功调用它可确保屏幕保护程序不会因用户不活动而启动,即在媒体播放期间。使用再次启用屏幕保护程序的方法可以反过来。这两种方法都可以在Windows 10(UWP Runtime)中使用,并且可以通过Windows.System.Display.DisplayRequest类访问。在WPF桌面应用程序中,如何在UWP应用程序之外使用它的示例如下所示。

在使用代码之前,有必要从模板中添加到Visual Studio中创建的标准WPF应用程序以下引用:(i)目录中的Windows.winmdC:\Program Files (x86)\Windows Kits\10\UnionMetadata和(ii){{1来自目录:System.Runtime.WindowsRuntime.dll(或v4.5)。

在播放过程中暂停或启用屏幕保护程序,将playaback事件的开始和停止挂钩到相关方法。

C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETCore\v4.5.1

可以在UwpDesktop NuGet package的网页上找到有关引用程序集以访问Windows 10 UWP API的另一条说明。 UwpDesktop软件包本身似乎在至少最后两次Windows 10 Creator更新的持续时间内没有维护,但如果定位10.0.14393 API则仍然有用。

有关如何从C / C ++代码使用此方法的示例,请参阅SDL Library的回购

<强> 2。自定义using System; using System.Windows; using System.Windows.Controls; using Windows.System.Display; namespace SuspendScreenSaverWpf { /// <summary> /// Interaction logic for MainWindow.xaml /// </summary> public partial class MainWindow : Window { private DisplayRequest mDisplayRequest; public MainWindow() { InitializeComponent(); } private void SuspendButton_Click(object sender, RoutedEventArgs e) { Button b = sender as Button; if (b != null) { try { if (mDisplayRequest == null) { // This call creates an instance of the displayRequest object mDisplayRequest = new DisplayRequest(); } } catch (Exception ex) { this.MessageBoard.Content = $"Error Creating Display Request: {ex.Message}"; } if (mDisplayRequest != null) { try { // This call activates a display-required request. If successful, // the screen is guaranteed not to turn off automatically due to user inactivity. mDisplayRequest.RequestActive(); this.MessageBoard.Content = $"Display request activated - ScreenSaver suspended"; this.EnableButton.IsEnabled = true; this.SuspendButton.IsEnabled = false; } catch (Exception ex) { this.MessageBoard.Content = $"Error: {ex.Message}"; } } } } private void EnableButton_Click(object sender, RoutedEventArgs e) { Button b = sender as Button; if (b != null) { if (mDisplayRequest != null) { try { // This call de-activates the display-required request. If successful, the screen // might be turned off automatically due to a user inactivity, depending on the // power policy settings of the system. The requestRelease method throws an exception // if it is called before a successful requestActive call on this object. mDisplayRequest.RequestRelease(); this.MessageBoard.Content = $"Display request released - ScreenSaver enabled."; this.SuspendButton.IsEnabled = true; this.EnableButton.IsEnabled = false; } catch (Exception ex) { this.MessageBoard.Content = $"Error: {ex.Message}"; } } } } } } <Window x:Class="SuspendScreenSaverWpf.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:SuspendScreenSaverWpf" mc:Ignorable="d" Title="MainWindow ScreenSaver management demo" Height="350" Width="525"> <Grid> <Button x:Name="SuspendButton" IsEnabled="true" Content="Suspend ScreenSaver" HorizontalAlignment="Left" Margin="73,250,0,0" VerticalAlignment="Top" Width="150" Click="SuspendButton_Click"/> <Button x:Name="EnableButton" IsEnabled="False" Content="Enable ScreenSaver" HorizontalAlignment="Left" Margin="298,250,0,0" VerticalAlignment="Top" Width="150" Click="EnableButton_Click"/> <Label x:Name="MessageBoard" Content="Example project demonstrating how to disable ScreenSaver on Windows 10" HorizontalAlignment="Left" Height="78" Margin="73,39,0,0" VerticalAlignment="Top" Width="375"/> </Grid> </Window>

问题的挑战部分与ScreenSaverManager控件的无证件行为有关,该控件在媒体播放期间暂停屏幕保护程序。尽管从用户观看电影的角度来看防止激活屏幕保护程序是一个好的做法,但是存在不希望的应用。由于安全原因或播放媒体时,强制禁用屏幕保护程序或工作站锁定并不是最重要的应用程序功能不是一个好习惯。 Microsoft应提供可用于控制此功能的公共属性方法。

为了避免任何黑客攻击,我试图仅使用Win32和.NET / WPF公共API来解决问题。最简单但最有效的解决方案是基于创建我们自己的System.Windows.Controls.MediaElement,它复制操作系统功能并强制执行按需原始屏幕保护程序行为(自动屏幕保护程序和自动注销等等)。首先ScreenSaverManager读取当前会话设置,并根据它们开始强制执行会话策略绕过用于阻止屏蔽保护程序和会话锁定ScreenSaverManager的方法。

MediaElement

Interop方法在单独的类using System; using System.Timers; namespace ManageScreenSaver.MediaElementWpf { public class ScreenSaverManager { private static ScreenSaverManager _Manager; public static ScreenSaverManager Instance { get { if (_Manager != null) return _Manager; _Manager = new ScreenSaverManager(); return _Manager; } } private TimeSpan _ScreenSaverTimeout; private bool _IsScreenSaverSecure; private Timer _Timer; protected ScreenSaverManager() { _ScreenSaverTimeout = NativeMethods.ScreenSaverTimeout; _IsScreenSaverSecure = NativeMethods.IsScreenSaverSecure; _Timer = new Timer(_ScreenSaverTimeout.TotalMilliseconds/2); _Timer.AutoReset = false; _Timer.Elapsed += Timer_Elapsed; _Timer.Start(); } private void Timer_Elapsed(object sender, ElapsedEventArgs e) { var lastInput = NativeMethods.GetLastUserInputTimeInterval(); MainWindow.Console.WriteLine($"Last user input interval: {lastInput}"); if (lastInput >= _ScreenSaverTimeout) { StartScreenSaver(); } else { _Timer.Interval = _ScreenSaverTimeout.Subtract(lastInput).TotalMilliseconds + 100; _Timer.Start(); } } private void StartScreenSaver() { if (_IsScreenSaverSecure) { NativeMethods.LockWorkStationSession(); } else { var result = NativeMethods.SendMessage((IntPtr) 0xffff, (uint) WindowMessage.WM_SYSCOMMAND, (uint) WmSysCommandParam.ScSCREENSAVE, 0); } } } } 中定义:

NativeMethods

最后有关于如何在WPF应用程序中使用using System; using System.ComponentModel; using System.Runtime.InteropServices; namespace ManageScreenSaver.MediaElementWpf { public static class NativeMethods { public const uint SPI_GETSCREENSAVETIMEOUT = 0x000E; public const uint SPI_SETSCREENSAVETIMEOUT = 0x000F; public const uint SPI_GETSCREENSAVEACTIVE = 0x0010; public const uint SPI_SETSCREENSAVEACTIVE = 0x0011; public const uint SPI_SETSCREENSAVERRUNNING = 0x0061; public const uint SPI_SCREENSAVERRUNNING = SPI_SETSCREENSAVERRUNNING; public const uint SPI_GETSCREENSAVERRUNNING = 0x0072; public const uint SPI_GETSCREENSAVESECURE = 0x0076; public const uint SPI_SETSCREENSAVESECURE = 0x0077; public const uint SPIF_UPDATEINIFILE = 0x0001; public const uint SPIF_SENDWININICHANGE = 0x0002; public const uint SPIF_SENDCHANGE = SPIF_SENDWININICHANGE; [DllImport("user32.dll", CallingConvention = CallingConvention.Winapi, PreserveSig = true, SetLastError = true)] internal static unsafe extern bool SystemParametersInfo(uint uiAction, uint uiParam, void* pvParam, uint fWinIni); [DllImport("user32.dll", CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Unicode, PreserveSig = true, SetLastError = true)] internal static extern IntPtr DefWindowProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled); [DllImport("user32.dll", SetLastError = true)] static extern bool GetLastInputInfo(ref LASTINPUTINFO plii); [DllImport("User32.dll", SetLastError = true)] internal static extern int SendMessage(IntPtr hWnd, uint msg, uint wParam, uint lParam); [DllImport("user32.dll", SetLastError = true)] internal static extern bool LockWorkStation(); public static TimeSpan GetLastUserInputTimeInterval() { LASTINPUTINFO lastInputInfo = new LASTINPUTINFO(); lastInputInfo.cbSize = (uint)Marshal.SizeOf(lastInputInfo); if (!GetLastInputInfo(ref lastInputInfo)) { throw new Win32Exception(Marshal.GetLastWin32Error()); } uint ticks = (uint)Environment.TickCount; var idleMiliseconds = ticks - lastInputInfo.dwTime; return idleMiliseconds > 0 ? TimeSpan.FromMilliseconds((double)idleMiliseconds) : default(TimeSpan); } public static void LockWorkStationSession() { if (!LockWorkStation()) { throw new Win32Exception(Marshal.GetLastWin32Error()); } } public static bool IsScreenSaverActive { get { bool enabled = false; unsafe { var result = SystemParametersInfo(SPI_GETSCREENSAVEACTIVE, 0, &enabled, 0); if (!result) { throw new Win32Exception(Marshal.GetLastWin32Error()); } return enabled; } } } public static bool IsScreenSaverRunning { get { bool enabled = false; unsafe { var result = SystemParametersInfo(SPI_GETSCREENSAVERRUNNING, 0, &enabled, 0); if (!result) { throw new Win32Exception(Marshal.GetLastWin32Error()); } return enabled; } } } public static bool IsScreenSaverSecure { get { bool enabled = false; unsafe { var result = SystemParametersInfo(SPI_GETSCREENSAVESECURE, 0, &enabled, 0); if (!result) { throw new Win32Exception(Marshal.GetLastWin32Error()); } return enabled; } } } public static TimeSpan ScreenSaverTimeout { get { int timeout = 0; unsafe { var result = SystemParametersInfo(SPI_GETSCREENSAVETIMEOUT, 0, &timeout, 0); if (!result) { throw new Win32Exception(Marshal.GetLastWin32Error()); } return TimeSpan.FromSeconds(timeout); } } } } [Flags] public enum WindowMessage : uint { WM_COMMAND = 0x0111, WM_SYSCOMMAND = 0x0112, } public enum WmSysCommandParam : uint { ScSIZE = 0xF000, ScMOVE = 0xF010, ScMINIMIZE = 0xF020, ScMAXIMIZE = 0xF030, ScNEXTWINDOW = 0xF040, ScPREVWINDOW = 0xF050, ScCLOSE = 0xF060, ScVSCROLL = 0xF070, ScHSCROLL = 0xF080, ScMOUSEMENU = 0xF090, ScKEYMENU = 0xF100, ScARRANGE = 0xF110, ScRESTORE = 0xF120, ScTASKLIST = 0xF130, ScSCREENSAVE = 0xF140, ScHOTKEY = 0xF150, ScDEFAULT = 0xF160, ScMONITORPOWER= 0xF170, ScCONTEXTHELP = 0xF180, ScSEPARATOR = 0xF00F, } } 的示例:

ScreenSaverManager

还有一个小实用程序缺少WPF控制台:

<Window x:Class="ManageScreenSaver.MediaElementWpf.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:ManageScreenSaver.MediaElementWpf"
        mc:Ignorable="d"
        Title="MainWindow" Height="570" Width="550" MinHeight="570" MinWidth="550" MaxHeight="570" MaxWidth="550">
    <Grid Margin="0,0,0,0">
        <MediaElement x:Name="myMediaElement" Width="530" Height="270" LoadedBehavior="Manual" HorizontalAlignment="Center"  VerticalAlignment="Center" IsMuted="True" Stretch="Fill" Margin="10,52,10,197" >
            <MediaElement.Triggers>
                <EventTrigger RoutedEvent="MediaElement.Loaded">
                    <EventTrigger.Actions>
                        <BeginStoryboard>
                            <Storyboard>
                                <MediaTimeline Source="..\..\BigBuckBunny_320x180.mp4" Storyboard.TargetName="myMediaElement" RepeatBehavior="Forever" />
                            </Storyboard>
                        </BeginStoryboard>
                    </EventTrigger.Actions>
                </EventTrigger>
            </MediaElement.Triggers>
        </MediaElement>
        <Button x:Name="SuspendButton" IsEnabled="true" Content="Suspend ScreenSaver" HorizontalAlignment="Left" Margin="74,489,0,0" VerticalAlignment="Top" Width="150" Click="SuspendButton_Click" RenderTransformOrigin="0.501,2.334"/>
        <Button x:Name="EnableButton" IsEnabled="true" Content="Enable ScreenSaver" HorizontalAlignment="Left" Margin="302,489,0,0" VerticalAlignment="Top" Width="150" Click="EnableButton_Click" RenderTransformOrigin="0.508,1.359"/>
        <Label x:Name="MessageBoard" Content="Example project demonstrating how to disable ScreenSaver on Windows 10" HorizontalAlignment="Left" Height="25" Margin="44,10,0,0" VerticalAlignment="Top"  Width="432"/>
        <TextBox x:Name="TextBox" Text="" HorizontalAlignment="Center" HorizontalContentAlignment="Left" Height="110" Margin="10,342,10,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="510"/>
    </Grid>
</Window>

using System;
using System.Timers;
using System.Windows;
using System.Windows.Controls;
using Windows.System.Display;

namespace ManageScreenSaver.MediaElementWpf
{
    public partial class MainWindow : Window
    {
        private DisplayRequest mDisplayRequest;
        internal static TextBoxWriter Console;
        private static ScreenSaverManager _ScreenSaverManager;

        public MainWindow()
        {
            InitializeComponent();
            Console = new TextBoxWriter(this.TextBox);
            _ScreenSaverManager = ScreenSaverManager.Instance;
            PrintSSaverStatus(" MainWindow.ctor");
        }

        private void PrintSSaverStatus(string apendedText = "")
        {
            Console.WriteLine(GetScreenSaverStatusMessage() + apendedText);
        }

        private void SuspendButton_Click(object sender, RoutedEventArgs e)
        {
            Button b = sender as Button;
            if (b != null)
            {
                EnsureDisplayRequest();

                if (mDisplayRequest != null)
                {
                    try
                    {
                        // This call activates a display-required request. If successful,
                        // the screen is guaranteed not to turn off automatically due to user inactivity.
                        mDisplayRequest.RequestActive();
                        this.MessageBoard.Content = $"Display request activated - ScreenSaver suspended";
                        this.EnableButton.IsEnabled = true;
                        this.SuspendButton.IsEnabled = false;
                    }
                    catch (Exception ex)
                    {
                        this.MessageBoard.Content = $"Error: {ex.Message}";
                    }

                    PrintSSaverStatus(" SuspendButton_Click");
                }
            }
        }

        private void EnsureDisplayRequest()
        {
            try
            {
                if (mDisplayRequest == null)
                {
                    // This call creates an instance of the displayRequest object
                    mDisplayRequest = new DisplayRequest();
                }
            }
            catch (Exception ex)
            {
                this.MessageBoard.Content = $"Error Creating Display Request: {ex.Message}";
            }
        }

        private void EnableButton_Click(object sender, RoutedEventArgs e)
        {
            Button b = sender as Button;
            if (b != null)
            {
                EnsureDisplayRequest();

                if (mDisplayRequest != null)
                {
                    try
                    {
                        // This call de-activates the display-required request. If successful, the screen
                        // might be turned off automatically due to a user inactivity, depending on the
                        // power policy settings of the system. The requestRelease method throws an exception
                        // if it is called before a successful requestActive call on this object.
                        mDisplayRequest.RequestRelease();
                        this.MessageBoard.Content = $"Display request released - ScreenSaver enabled.";
                        this.SuspendButton.IsEnabled = true;
                        this.EnableButton.IsEnabled = false;
                    }
                    catch (Exception ex)
                    {
                        this.MessageBoard.Content = $"Error: {ex.Message}";
                    }

                    PrintSSaverStatus(" EnableButton_Click");
                }
            }
        }

        private string GetScreenSaverStatusMessage()
        {
            string message = $"Screen Saver is: \"{{0}}\", \"using System;
using System.IO;
using System.Text;
using System.Windows.Controls;

namespace ManageScreenSaver.MediaElementWpf
{
    public class TextBoxWriter : TextWriter
    {

        private TextBox _TextBox;
        private string _NewLine = "\n";

        public TextBoxWriter(TextBox target)
        {
            if (target == null)
                throw new ArgumentNullException(nameof(target));

            _TextBox = target;
        }

        public override Encoding Encoding => new UTF8Encoding(false);

        public override string NewLine { get => _NewLine; set => _NewLine = value; }

        public override void Write(string value)
        {
            _TextBox.Dispatcher.InvokeAsync(
                () => _TextBox.AppendText(value)
                );
        }

        public override void WriteLine(string value)
        {
            _TextBox.Dispatcher.InvokeAsync(
                () => _TextBox.AppendText(value + NewLine)
                );
        }
    }
}
\", timeout: \"{{2}}\"  {DateTime.UtcNow}";
            message = String.Format(message,
                NativeMethods.IsScreenSaverActive ? "active" : "inactive",
                NativeMethods.IsScreenSaverSecure ? "secure" : "not secure",
                NativeMethods.ScreenSaverTimeout);
            return message;
        }
    }
}

注意:这不是生产就绪代码。有必要实现错误处理,系统和监视器电源事件处理,登录和注销处理:工作站会话开始和结束,会话屏幕保护程序配置更改。

此方法有一些限制,在通过RDP连接到远程计算机时无效,但在通过控制台连接到Hyper-V中的Windows VM时可以正常工作。