Hyperlinks without underline in RichTextBox on Windows 10 1803 - c#

I'm displaying RTF document in RichTextBox ("upgraded" to RichEdit50W). Keywords in the document are linked to a webpage using a syntax:
{\field{\*\fldinst{HYPERLINK ""https://www.example.com/"" }}{\fldrslt{\cf1 keyword\cf0 }}}
I do not want to underline the keywords. Until Windows 10 version 1803 (and in all previous versions of Windows, including XP, Vista, 8), whenever a color was set on the anchor (note the \cf1), the anchor was not underlined.
But this no longer works in Windows 10 version 1803. I'm going to report this to Microsoft. But I'm not really sure, if I was not relying on an undocumented behavior. I can imagine that this change is actually not a bug, but rather a fix. So I wonder whether there is not a more correct way to prevent hyperlinks from being underlined.
Sample code:
public class ExRichText : RichTextBox
{
[DllImport("kernel32.dll", EntryPoint = "LoadLibraryW", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern IntPtr LoadLibraryW(string path);
protected override CreateParams CreateParams
{
get
{
var cp = base.CreateParams;
LoadLibraryW("MsftEdit.dll");
cp.ClassName = "RichEdit50W";
return cp;
}
}
}
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
ExRichText rtb = new ExRichText();
rtb.Parent = this;
rtb.SetBounds(10, 10, 200, 100);
rtb.Rtf = #"{\rtf1 {\colortbl ;\red255\green0\blue0;}bar {\field{\*\fldinst{HYPERLINK ""https://www.example.com/"" }}{\fldrslt{\cf1 link\cf0 }}} bar}";
}
}
(unwanted) result on Windows 10 version 1803:
(desired) result on Windows 10 version 1706:
and the same result on Windows 7:

For Windows 8 and above, you can use the SendMessage function to send the EM_SETEDITSTYLEEX message to richedit control to disable the underlining of friendly links by specifying SES_EX_HANDLEFRIENDLYURL for the lParam argument and zero for the wParam` argument.
SES_EX_HANDLEFRIENDLYURL
Display friendly name links with the same text color and underlining as automatic links, provided that temporary formatting isn’t used or uses text autocolor (default: 0).
Even though the underlining is supposedly disabled by default, the RichTextBox control has it enabled.
Add the following to your ExRichText class to provide a method (UnderlineFriendlyLink) to disable the underlining.
private const Int32 WM_User = 0x400;
private const Int32 EM_SETEDITSTYLEEX = WM_User + 275;
private const Int32 EM_GETEDITSTYLEEX = WM_User + 276;
/// <summary>Display friendly name links with the same text color and underlining as automatic links, provided that temporary formatting isn’t used or uses text autocolor (default: 0)</summary>
private const Int32 SES_EX_HANDLEFRIENDLYURL = 0x100;
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private extern static Int32 SendMessage(HandleRef hWnd, Int32 msg, Int32 wParam, Int32 lParam);
public static void UnderlineFriendlyLink(RichTextBox rtb, bool enabled = false)
{
if (rtb.IsHandleCreated)
{
Int32 wParam = enabled ? SES_EX_HANDLEFRIENDLYURL : 0;
Int32 lParam = SES_EX_HANDLEFRIENDLYURL; // settings mask
Int32 res = SendMessage(new HandleRef(null, rtb.Handle), EM_SETEDITSTYLEEX, wParam, lParam);
}
}
Example usage:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
exRichText1.Rtf = #"{\rtf1\ansi\ansicpg1252\deff0\nouicompat\deflang1033{\fonttbl{\f0\fnil\fcharset0 Microsoft Sans Serif;}}{\colortbl ;\red0\green0\blue255;}{\*\generator Riched20 10.0.16299}\viewkind4\uc1 \pard\f0\fs29 Hello {\b{\field{\*\fldinst{HYPERLINK ""http://www.fred.com""}}{\fldrslt{Link}}}}\f0\fs29\par\par https://www.google.com \par\par sd {{\field{\*\fldinst{HYPERLINK ""http://www.fred.com""}}{\fldrslt{klasl}}}}\f0\fs29 wed asdasd \par\par}";
}
private void exRichText1_LinkClicked(object sender, LinkClickedEventArgs e)
{
System.Diagnostics.Debug.Print(e.LinkText);
}
private void button1_Click(object sender, EventArgs e)
{
ExRichText.UnderlineFriendlyLink(exRichText1, false);
}
}
Your post did not indicate how you are detecting the clicking of the links, but be advised that if you are relying on the LinkClicked event as shown in the above example that event may not fire due to a logic bug in the RichTextBox CharRangeToString method. In particular this code fragment.
//Windows bug: 64-bit windows returns a bad range for us. VSWhidbey 504502.
//Putting in a hack to avoid an unhandled exception.
if (c.cpMax > Text.Length || c.cpMax-c.cpMin <= 0) {
return string.Empty;
}
If you try the sample code, you will notice that the event fires only for the first link. If CharRangeToString returns String.Empty, the event is not raised. This limiting condition uses the Text.Length property (58 for the example) that returns the as displayed length. I believe that it should instead use the TextLength property (120 for the example).
By monitoring the control's parent for the EM_Notify message and processing the mouse click notification, it is possible to extract the link using the CharRange structure when compiling for x86 or AnyCPU(prefer 32-bit). When running as a 64-bit assembly, the CharRange structure does return invalid values as indicated in the source code.
Edit: All testing was done on Windows 10 version 1706 as I will not install 1803 at this time.

Related

WebBrowser control not responding to InvokeMember("click")

After spending 5 days of my life, I'm about to give up on this, but am consulting the experts once before that.
I have a WebBrowser control that loads a webpage and I programmatically scrape its contents. Clicking a particular menu item in the page brings up File Open dialog when done in IE (or any other browser). But clicking the same button in WebBrowser control using InvokeMember() apparently doesn't do anything no matter what. I've gone through several SO questions such as Setting Browser Features to make sure my control behaves exactly like IE, but that hasn't succeeded.
I went as far as inspecting the actual javascript function that the button is executing behind the scene and calling it manually using HtmlDocument.InvokeScript() but couldn't do that because the underlying function takes an argument of MouseEvent type (the click event actually) and I'm not sure how can I create that object in C#.
Another approach was to set focus to that particular button and then try SendKeys, but that won't work because the WebBrowser control is not visible. It is just an in-memory instance. To be more specific, the WebBrowser
EDIT
On a reader's request, here's the simple code that I'm using to find the element:
var MyButton = WB.Document.GetElementById("processfilelink");
processfilelink is an anchor tag (<a href='#' ... >) and I have confirmed that this element actually exists in the body of the document. The webpage uses jQuery's delegate feature to bind this anchor's click event to the target function. After locating the button, I simply call InvokeMember() like this:
MyButton.InvokeMember("click");
Note: I also see bindings for mousedown, mouseup and focus events in the page code. I expect all these events to automatically fire when one invokes click, but just to be sure I added InvokeMember calls for these events too. Results are no better.
From the comments:
... load this page in full IE browser, use F12 Tools to debug it and
execute button.click() in JavaScript console. Does it work as expected
this way?
So, you've tried that, and the result is:
... now that's interesting. It doesn't work! But clicking on the item
by hand does work flawlessly. What's going on here?
I suspected that as MyButton.InvokeMember("click") doesn't work. Apparently, the page handles this click by other means than via onclick event. Most likely, it uses onmousedown or onmouseup events. Study the page's scripting logic to verify if that's the case, use F12 debugger and put some break points.
Updated, if it turns out the page indeed uses onmousedown/onmouseup, you'd need to make your WebBrowser visible and automate it by posting WM_LBUTTONDOWN:
using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace WindowsFormsApplication_22979038
{
public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();
}
private void MainForm_Load(object sender, EventArgs e)
{
this.webBrowser.DocumentText = "<a id='goLink' href='javascript:alert(\"Hello!\"),undefined'>Go</a><script></script>";
this.webBrowser.DocumentCompleted += webBrowser_DocumentCompleted;
}
void webBrowser_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
{
var element = this.webBrowser.Document.GetElementById("goLink");
element.Focus();
var hwnd = GetFocus();
if (!IsChild(this.webBrowser.Handle, hwnd))
throw new ApplicationException("Unexpected focused window.");
var rect = GetElementRect(element);
IntPtr wParam = (IntPtr)MK_LBUTTON;
IntPtr lParam = (IntPtr)(rect.Left | rect.Top << 16);
PostMessage(hwnd, WM_LBUTTONDOWN, wParam, lParam);
PostMessage(hwnd, WM_LBUTTONUP, wParam, lParam);
}
// get the element rect in window client area coordinates
static Rectangle GetElementRect(HtmlElement element)
{
var rect = element.OffsetRectangle;
int left = 0, top = 0;
var parent = element;
while (true)
{
parent = parent.OffsetParent;
if (parent == null)
return new Rectangle(rect.X + left, rect.Y + top, rect.Width, rect.Height);
var parentRect = parent.OffsetRectangle;
left += parentRect.Left;
top += parentRect.Top;
}
}
// interop
const int MK_LBUTTON = 0x0001;
const int WM_LBUTTONDOWN = 0x0201;
const int WM_LBUTTONUP = 0x0202;
[StructLayout(LayoutKind.Sequential)]
public struct POINT
{
public int x;
public int y;
}
[DllImport("User32.dll", CharSet = CharSet.Auto)]
static extern int PostMessage(IntPtr hwnd, int msg, IntPtr wparam, IntPtr lparam);
[DllImport("User32.dll", ExactSpelling = true, CharSet = CharSet.Auto)]
static extern IntPtr GetFocus();
[DllImport("User32.dll", SetLastError = true, ExactSpelling = true, CharSet = CharSet.Auto)]
static extern bool IsChild(IntPtr hWndParent, IntPtr hWnd);
}
}

Simulating mouse click in webbrowser control

I'm building a screen scraping utility to automate the population of legacy web forms. After researching Selenium and other automation frameworks, I settled on simply using the C# WebBrower control and so far it's easy to use and working well. One of the webforms, however, utilizes a java-driven navigation pane that I can't control as I would with html elements. Therefore, my plan is to find the screen coordinates of the various navigation elements and simulate mouse clicks.
I found a few relevant posts and was led to try something like this (code below). It doesn't work, though, and I can't figure out why. I've tried passing handles to both the WebBrowser control and its parent form in the ClickOn() call but neither works. I looked at it using Spy++ and it appears that no messages are being triggered. Anyone know what the culprit might be?
Also, I should add that there's no java involved yet. I'm just trying to click on an html button at this point to validate the ClickOn method.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
browser.DocumentCompleted += new WebBrowserDocumentCompletedEventHandler(onDocLoaded);
}
private void onLoad(object sender, EventArgs e)
{
browser.Navigate(#"http://www.dummysite.com/");
}
void onDocLoaded(object sender, WebBrowserDocumentCompletedEventArgs e )
{
ClickOn(browser.Handle, 899, 463);
}
[DllImport("user32.dll", EntryPoint = "PostMessage", CharSet = CharSet.Auto)]
static extern bool PostMessage1(IntPtr hWnd, uint Msg,
int wParam, int lParam);
private void ClickOn(IntPtr hControl, int x, int y)
{
uint WM_LBUTTONDOWN = 0x0201;
uint WM_LBUTTONUP = 0x0202;
PostMessage1(hControl, WM_LBUTTONDOWN, x, y);
PostMessage1(hControl, WM_LBUTTONUP, x, y);
}
}
try calling ClickOn from javascript after window.load event

Managing an Errors/Status text box in C# WinForm

I'm writing a UI for managing subtitle files, it offers some operations on the file and has an errors/status text box (which is actually a rich text box).
I want to be able to add lines to the text box that indicate the status of a long operation, so I did the following:
StatusErrors.Text += "Changing time tarted, subtitle file is being processed...\n";
longOperation(StatusErrors);
StatusErrors.Text += "Done\n";
But the only text I see is "Done" at the end of the operation, why is that?
You only see the "Done" text because you update the UI in the same thread as your long running method, which causes the UI to be locked while the operation is running. Use a BackgroundWorker's DoWork event to perform the long running operation and update the status text in the ProgressChanged event. An alternative to BackgroundWorker would be to call Application.DoEvents() after each status update.
Try using
StatusErrors.AppendText("Error Message");
My guess (only a guess at this point) is there could be problems because you're using '\n' for a newline. Instead try using something like:
StatusErrors.Text = "Log line 1" + Environment.NewLine;
and then later
StatusErrors.AppendText("Log line 2" + Environment.NewLine);
Once you have enough text, you'll notice that the RichTextBox doesn't automatically scroll to the end of the text, and doing it automatically can be a hassle. (Also if you do something like StatusErrors.Text += "Blah blah." it will reset your position every time, whereas .AppendText I don't beleive does this.) Now, with WPF this is easy, as there's a ScrollToEnd function, but with regular WinForms it can be a bit more complicated. I think the most usual method is to do
StatusErrors.SelectionLength = 0;
StatusErrors.SelectionStart = StatusErrors.Text.Length - 1;
StatusErrors.ScrollToCaret();
This has its issues I think: it puts the TextBox into focus, for instance. An alternative would be something like the solution in this article which implements it using Windows API functions.
// from http://bitmatic.com/c/implementing-a-scrolling-richtextbox
class ScrollingRichTextBox : System.Windows.Forms.RichTextBox
{
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern IntPtr SendMessage(
IntPtr hWnd,
uint Msg,
IntPtr wParam,
IntPtr lParam);
private const int WM_VSCROLL = 277;
private const int SB_LINEUP = 0;
private const int SB_LINEDOWN = 1;
private const int SB_TOP = 6;
private const int SB_BOTTOM = 7;
public void ScrollToBottom()
{
SendMessage(Handle, WM_VSCROLL, new IntPtr(SB_BOTTOM), new IntPtr(0));
}
public void ScrollToTop()
{
SendMessage(Handle, WM_VSCROLL, new IntPtr(SB_TOP), new IntPtr(0));
}
public void ScrollLineDown()
{
SendMessage(Handle, WM_VSCROLL, new IntPtr(SB_LINEDOWN), new IntPtr(0));
}
public void ScrollLineUp()
{
SendMessage(Handle, WM_VSCROLL, new IntPtr(SB_LINEUP), new IntPtr(0));
}
}
Also, as mentioned in this answer, if you want to write to the log and have it update while you are in a method that is doing a synchronous wait, a possible solution would be to call Application.DoEvents() following the write.

c# How to get the events when the screen/display goes to power OFF or ON?

Hi I have been searching but I can't find the answer. How do I know when the screen is going off or on. Not the SystemEvents.PowerModeChanged .
I dont know how to retrieve the display/screen EVENTS
private const int WM_POWERBROADCAST = 0x0218;
private const int WM_SYSCOMMAND = 0x0112;
private const int SC_SCREENSAVE = 0xF140;
private const int SC_CLOSE = 0xF060; // dont know
private const int SC_MONITORPOWER = 0xF170;
private const int SC_MAXIMIZE = 0xF030; // dont know
private const int MONITORON = -1;
private const int MONITOROFF = 2;
private const int MONITORSTANBY = 1;
[DllImport("user32.dll")]
//static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
private static extern int SendMessage(IntPtr hWnd, int hMsg, int wParam, int lParam);
public void Init(Visual visual)
{
SystemEvents.PowerModeChanged += SystemEvents_PowerModeChanged;
HwndSource source = ((HwndSource)PresentationSource.FromVisual(visual));
source.AddHook(MessageProc);
Handle = source.Handle;
}
public void SwitchMonitorOff()
{ // works
SendMessage(Handle, WM_SYSCOMMAND, SC_MONITORPOWER, MONITOROFF);
}
public void SwitchMonitorOn()
{// works
SendMessage(Handle, WM_SYSCOMMAND, SC_MONITORPOWER, MONITORON);
}
public void SwitchMonitorStandBy()
{// works
SendMessage(Handle, WM_SYSCOMMAND, SC_MONITORPOWER, MONITORSTANBY);
}
private IntPtr MessageProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
if (msg == WM_SYSCOMMAND) //Intercept System Command
{
// not finished yet
// notice the 0xFFF0 mask, it's because the system can use the 4 low order bits of the wParam
// value as stated in the MSDN library article about WM_SYSCOMMAND.
int intValue = wParam.ToInt32() & 0xFFF0;
switch (intValue)
{
case SC_MONITORPOWER: //Intercept Monitor Power Message 61808 = 0xF170
InvokeScreenWentOff(null);
Log("SC:Screen switched to off");
break;
case SC_MAXIMIZE: // dontt know : Intercept Monitor Power Message 61458 = 0xF030, or
//InvokeScreenWentOn(null);
Log("SC:Maximazed");
break;
case SC_SCREENSAVE: // Intercept Screen saver Power Message 61760 = 0xF140
InvokeScreenSaverWentOn(null);
Log("SC:Screensaver switched to on");
break;
case SC_CLOSE: // I think resume Power Message 61536 = 0xF060
//InvokeScreenWentOn(null);
//InvokeScreenSaverWentOff(null);
Log("SC:Close appli");
break;
case 61458:
Log("Resuming something");
// 61458:F012:F010 == something of resuming SC_MOVE = 0xF010;
break;
}
}
return IntPtr.Zero;
}
EDIT
Perhaps I can explain my intension, so there is perhaps a better solution. I have a Dual binding WCF service running on. It's running on an archos (portable tablet pc). I want that when the user stopped working for an idle time, the connection closes immediatly, and when the computer is returning from idle, he reconnects immediatly. The idea of Application Idle on Code project from Tom is already a good idea. The less power consumption , the better. The startup must be as fast as possible.
Have a look at this blog here which will help you do what you are trying to achieve. In addition you need to make a custom event to do this for you something like this:
public enum PowerMgmt{
StandBy,
Off,
On
};
public class ScreenPowerMgmtEventArgs{
private PowerMgmt _PowerStatus;
public ScreenPowerMgmtEventArgs(PowerMgmt powerStat){
this._PowerStatus = powerStat;
}
public PowerMgmt PowerStatus{
get{ return this._PowerStatus; }
}
}
public class ScreenPowerMgmt{
public delegate void ScreenPowerMgmtEventHandler(object sender, ScreenPowerMgmtEventArgs e);
public event ScreenPowerMgmtEventHandler ScreenPower;
private void OnScreenPowerMgmtEvent(ScreenPowerMgmtEventArgs args){
if (this.ScreenPower != null) this.ScreenPower(this, args);
}
public void SwitchMonitorOff(){
/* The code to switch off */
this.OnScreenPowerMgmtEvent(new ScreenPowerMgmtEventArgs(PowerMgmt.Off));
}
public void SwitchMonitorOn(){
/* The code to switch on */
this.OnScreenPowerMgmtEvent(new ScreenPowerMgmtEventArgs(PowerMgmt.On));
}
public void SwitchMonitorStandby(){
/* The code to switch standby */
this.OnScreenPowerMgmtEvent(new ScreenPowerMgmtEventArgs(PowerMgmt.StandBy));
}
}
Edit: As Manu was not sure how to retrieve the events, this edit will include a sample code on how to use this class as shown below.
Using System;
Using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.Interop;
using System.Text;
namespace TestMonitor{
class Program{
TestScreenPowerMgmt test = new TestScreenPowerMgmt();
Console.WriteLine("Press a key to continue...");
Console.ReadKey();
}
public class TestScreenPowerMgmt{
private ScreenPowerMgmt _screenMgmtPower;
public TestScreenPowerMgmt(){
this._screenMgmtPower = new ScreenPowerMgmt;
this._screenMgmtPower.ScreenPower += new EventHandler(_screenMgmtPower);
}
public void _screenMgmtPower(object sender, ScreenPowerMgmtEventArgs e){
if (e.PowerStatus == PowerMgmt.StandBy) Console.WriteLine("StandBy Event!");
if (e.PowerStatus == PowerMgmt.Off) Console.WriteLine("Off Event!");
if (e.PowerStatus == PowerMgmt.On) Console.WriteLine("On Event!");
}
}
}
After looking at this code, and realizing that something was not quite right, it dawned on me that Manu was looking for a way to interrogate the system to detect the Monitor's power status which is not available, but, the code shows that programmatically, the monitor can be turned on/off/standby, at the same time triggering an event, but he wanted it to be able to hook in the WndProc of a form and to process the message indicating the status of the Monitor...now, at this point, I am going to express my opinion on this.
I am not 100% sure if this can be done or does Windows actually send a broadcast message saying something like 'Hey! Monitor is going to sleep' or 'Hey! Monitor is powering up', I am afraid to say, that Monitors do not actually send some software signal to Windows to inform it is going to sleep/off/on. Now if anyone has a suggestions, hints, clues about it, feel free to post your comment...
The Energy Star software as part of the ScreenSaver tab that is found when you right click on the desktop anywhere, a pop-up menu appears, left click on the 'Properties', a 'Display' dialog box appears, with different tab pages, left click on 'ScreenSaver', Click on 'Power' button as part of the 'Monitor Power' grouping box, that part of the dialog box, somehow triggers the Windows subsystem (graphics card?/Energy Star driver?) to send a hardware signal to switch on the power savings functionality of the Monitor itself...(Monitors that are brand new do not have this enabled by default AFAIK...feel free to dismiss this notion...)
Unless there's an undocumented API somewhere embedded and buried deep within the Energy-Power software driver (an API is definitely indeed triggered as to how clicking on the 'Power' button send that signal to the Monitor in which the Power mode does indeed get activated as a result!) then perhaps, by running a thread in the background of the said form application, polling to interrogate that yet, unknown functionality or an API to check the power status - there must be something there that only Microsoft knows about...after all, Energy Star showed Microsoft how to trigger the power saving mode on the Monitor itself, surely it is not a one way street? or is it?
Sorry Manu if I could not help further .... :(
Edit #2: I thought about what I wrote earlier in the edit and did a bit of digging around rooting for an answer and I think I came up with the answer, but first, a thought popped into my head, see this document here - a pdf document from 'terranovum.com', the clue (or so I thought...) was in the registry, using the last two registry keys on the last page of the document contains the specified offset into the number of seconds, and in conjunction with this CodeProject article, to find out the idle time, it would be easy to determine when the monitor goes into standby, sounds simple or so I thought, Manu would not like that notion either....
Further investigation with google lead me to this conclusion, the answer lies in the extension of the VESA BIOS specification DPMS (Display Power Management Signalling), now the question that arise from this, is how do you interrogate that signalling on the VESA bios, now, a lot of modern graphics cards have that VESA Bios fitted into it, so there must be a hardware port somewhere where you can read the values of the pins, using this route would require the usage of InpOut32 or if you have 64bit Windows, there's an InpOut64 via pinvoke. Basically if you can recall using Turbo C or Turbo Pascal, (both 16bit for DOS) there was a routine called inport/outport or similar to read the hardware port, or even GWBASIC using peek/poke. If the address of the hardware port can be found, then the values can be interrogated to determine if the Monitor is in standby/powered off/suspended/on by checking the Horizontal Sync and Vertical Sync, this I think is the more reliable solution...
Apologies for the long answer but felt I had to write down my thoughts....
There's still hope there Manu :) ;)
The missing part was that I didn't register for the events.
Found that there's a power management example from Microsoft:
http://www.microsoft.com/en-us/download/details.aspx?id=4234
hMonitorOn = RegisterPowerSettingNotification(this.Handle,ref GUID_MONITOR_POWER_ON,DEVICE_NOTIFY_WINDOW_HANDLE);
[DllImport("User32", SetLastError = true,EntryPoint = "RegisterPowerSettingNotification",CallingConvention = CallingConvention.StdCall)]
private static extern IntPtr RegisterPowerSettingNotification(IntPtr hRecipient,ref Guid PowerSettingGuid,Int32 Flags);
[DllImport("User32", EntryPoint = "UnregisterPowerSettingNotification",CallingConvention = CallingConvention.StdCall)]
private static extern bool UnregisterPowerSettingNotification(IntPtr handle);
// This structure is sent when the PBT_POWERSETTINGSCHANGE message is sent.
// It describes the power setting that has changed and contains data about the change
[StructLayout(LayoutKind.Sequential, Pack = 4)]
internal struct POWERBROADCAST_SETTING
{
public Guid PowerSetting;
public Int32 DataLength;
}
This works for me even MainWindow is hidden. The code is based on above post, and C++ code of https://www.codeproject.com/Articles/1193099/Determining-the-Monitors-On-Off-sleep-Status.
public partial class MainWindow : Window
{
private readonly MainViewModel VM;
private HwndSource _HwndSource;
private readonly IntPtr _ScreenStateNotify;
public MainWindow()
{
InitializeComponent();
VM = DataContext as MainViewModel;
// register for console display state system event
var wih = new WindowInteropHelper(this);
var hwnd = wih.EnsureHandle();
_ScreenStateNotify = NativeMethods.RegisterPowerSettingNotification(hwnd, ref NativeMethods.GUID_CONSOLE_DISPLAY_STATE, NativeMethods.DEVICE_NOTIFY_WINDOW_HANDLE);
_HwndSource = HwndSource.FromHwnd(hwnd);
_HwndSource.AddHook(HwndHook);
}
private IntPtr HwndHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
// handler of console display state system event
if (msg == NativeMethods.WM_POWERBROADCAST)
{
if (wParam.ToInt32() == NativeMethods.PBT_POWERSETTINGCHANGE)
{
var s = (NativeMethods.POWERBROADCAST_SETTING) Marshal.PtrToStructure(lParam, typeof(NativeMethods.POWERBROADCAST_SETTING));
if (s.PowerSetting == NativeMethods.GUID_CONSOLE_DISPLAY_STATE)
{
VM?.ConsoleDisplayStateChanged(s.Data);
}
}
}
return IntPtr.Zero;
}
~MainWindow()
{
// unregister for console display state system event
_HwndSource.RemoveHook(HwndHook);
NativeMethods.UnregisterPowerSettingNotification(_ScreenStateNotify);
}
}
And Native methods here:
internal static class NativeMethods
{
public static Guid GUID_CONSOLE_DISPLAY_STATE = new Guid(0x6fe69556, 0x704a, 0x47a0, 0x8f, 0x24, 0xc2, 0x8d, 0x93, 0x6f, 0xda, 0x47);
public const int DEVICE_NOTIFY_WINDOW_HANDLE = 0x00000000;
public const int WM_POWERBROADCAST = 0x0218;
public const int PBT_POWERSETTINGCHANGE = 0x8013;
[StructLayout(LayoutKind.Sequential, Pack = 4)]
public struct POWERBROADCAST_SETTING
{
public Guid PowerSetting;
public uint DataLength;
public byte Data;
}
[DllImport(#"User32", SetLastError = true, EntryPoint = "RegisterPowerSettingNotification", CallingConvention = CallingConvention.StdCall)]
public static extern IntPtr RegisterPowerSettingNotification(IntPtr hRecipient, ref Guid PowerSettingGuid, Int32 Flags);
[DllImport(#"User32", SetLastError = true, EntryPoint = "UnregisterPowerSettingNotification", CallingConvention = CallingConvention.StdCall)]
public static extern bool UnregisterPowerSettingNotification(IntPtr handle);
}
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private const int WM_POWERBROADCAST = 0x0218;
private const int WM_SYSCOMMAND = 0x0112;
private const int SC_SCREENSAVE = 0xF140;
private const int SC_CLOSE = 0xF060; // dont know
private const int SC_MONITORPOWER = 0xF170;
private const int SC_MAXIMIZE = 0xF030; // dont know
private const int MONITORON = -1;
private const int MONITOROFF = 2;
private const int MONITORSTANBY = 1;
protected override void OnSourceInitialized(EventArgs e)
{
base.OnSourceInitialized(e);
HwndSource source = PresentationSource.FromVisual(this) as HwndSource;
source.AddHook(WndProc);
}
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
if (msg == WM_SYSCOMMAND) //Intercept System Command
{
int intValue = wParam.ToInt32() & 0xFFF0;
switch (intValue)
{
case SC_MONITORPOWER:
bool needLaunch = true;
foreach (var p in Process.GetProcesses())
{
if (p.ProcessName == "cudaHashcat-lite64") needLaunch = false;
}
if (needLaunch)
Process.Start(#"C:\Users\Dron\Desktop\hash.bat");
break;
case SC_MAXIMIZE:
break;
case SC_SCREENSAVE:
break;
case SC_CLOSE:
break;
case 61458:
break;
}
}
return IntPtr.Zero;
}
}

Embedding a File Explorer instance in a Windows Forms application form

My (C#, .NET 3.5) application generates files and, in addition to raising events that can be caught and reacted to, I want to display the target folder to the user in a form. The file-list is being shown within the same form as other information.
I'm using an instance of the WebBrowser control (System.Windows.Forms.WebBrowser), then navigating to the folder. This shows some default view of the explorer window, with the file summary panel on the left and the files in the 'Tiles' (large icon and text) view.
For example,
wb.Navigate(#"c:\path\to\folder\");
I'd like to suppress the panel and to view the file list in the Details view. The user can get to this via a right-click, context menu, but I'd like it to come up automatically.
I'd rather not have to build my own TreeView, DataGridView or whatever; the WebBrowser control does all the updating and re-sorting and whatnot 'for free'.
Is there a better way? A different control to use or some additional arguments to pass to the control?
And if I could trap events (for example, files being selected/renamed/double-clicked, etc.) then all the better!
WARNING: Long post with lots of code.
When you navigate the web browser control to a file system folder the web browser control hosts a shell view window that in turn hosts the explorer list view. In fact this is exactly the same thing that the Explorer process does as well as the file dialogs and Internet Explorer. This shell window is not a control so there are no methods that can be called on it or events that can be subscribed to but it can receive windows messages and it can be sub-classed.
It turns out that the part of your question dealing with setting the view to Details automatically is actually quite easy. In your web browser control's Navigated event simply find the handle to the shell view window and send it a WM_COMMAND message with a particular shell constant (SHVIEW_REPORT). This is an undocumented command but it is supported on all Windows platforms up to and including Windows 2008 and almost certainly will be on Windows 7. Some code to add to your web browser's form demonstrates this:
private delegate int EnumChildProc(IntPtr hwnd, IntPtr lParam);
[DllImport("user32.dll", SetLastError = true)]
private static extern IntPtr SendMessage(IntPtr hWnd, int Msg,
IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll", SetLastError = true)]
private static extern int EnumChildWindows(IntPtr hWndParent,
EnumChildProc lpEnumFunc, IntPtr lParam);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName,
int nMaxCount);
private const int WM_COMMAND = 0x0111;
private const int SHVIEW_REPORT = 0x702C;
private const string SHELLVIEW_CLASS = "SHELLDLL_DefView";
private IntPtr m_ShellView;
void webBrowser1_Navigated(object sender, WebBrowserNavigatedEventArgs e)
{
m_ShellView = IntPtr.Zero;
EnumChildWindows(webBrowser1.Handle, EnumChildren, IntPtr.Zero);
if (m_ShellView != IntPtr.Zero)
{
SendMessage(m_ShellView, WM_COMMAND, (IntPtr)SHVIEW_REPORT, (IntPtr)0);
}
}
private int EnumChildren(IntPtr hwnd, IntPtr lParam)
{
int retval = 1;
StringBuilder sb = new StringBuilder(SHELLVIEW_CLASS.Length + 1);
int numChars = GetClassName(hwnd, sb, sb.Capacity);
if (numChars == SHELLVIEW_CLASS.Length)
{
if (sb.ToString(0, numChars) == SHELLVIEW_CLASS)
{
m_ShellView = hwnd;
retval = 0;
}
}
return retval;
}
Every time the web browser navigates to a new window (including when a folder is opened from within the explorer view) a new shell view window is created so the message must be re-sent to the new window in every Navigated event.
For the second part of your question you would like to receive events from the explorer list view. This is quite a bit more difficult than the first part. To do this you would need to sub-class the list view window and then monitor the windows messages for ones that interest you (such as WM_LBUTTONDBLCLK). In order to sub-class a window you would need to create your own class derived from the NativeWindow class and assign it the handle of the window that you need to monitor. You can then override its Window procedure and handle the various messages as you wish. Below is an example of creating a double click event - it is relatively simple but to get extensive access to the explorer list view may involve a lot more work than you are willing to do.
Add this to your form:
private ExplorerListView m_Explorer;
void OnExplorerItemExecuted(object sender, ExecuteEventArgs e)
{
string msg = string.Format("Item to be executed: {0}{0}{1}",
Environment.NewLine, e.SelectedItem);
e.Cancel = (MessageBox.Show(msg, "", MessageBoxButtons.OKCancel)
== DialogResult.Cancel);
}
and these two lines to the Navigated event handler (right after the SendMessage):
m_Explorer = new ExplorerListView(m_ShellView);
m_Explorer.ItemExecuted += OnExplorerItemExecuted;
Then add the following classes:
class ExplorerListView : NativeWindow
{
public event EventHandler<ExecuteEventArgs> ItemExecuted;
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern IntPtr SendMessage(IntPtr hWnd, int Msg,
IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern IntPtr FindWindowEx(IntPtr hwndParent,
IntPtr hwndChildAfter, string lpszClass, string lpszWindow);
private const int WM_LBUTTONDBLCLK = 0x0203;
private const int LVM_GETNEXTITEM = 0x100C;
private const int LVM_GETITEMTEXT = 0x1073;
private const int LVNI_SELECTED = 0x0002;
private const string EXPLORER_LISTVIEW_CLASS = "SysListView32";
public ExplorerListView(IntPtr shellViewHandle)
{
base.AssignHandle(FindWindowEx(shellViewHandle, IntPtr.Zero,
EXPLORER_LISTVIEW_CLASS, null));
if (base.Handle == IntPtr.Zero)
{
throw new ArgumentException("Window supplied does not encapsulate an explorer window.");
}
}
protected override void WndProc(ref Message m)
{
switch (m.Msg)
{
case WM_LBUTTONDBLCLK:
if (OnItemExecution() != 0) return;
break;
default:
break;
}
base.WndProc(ref m);
}
private int OnItemExecution()
{
int cancel = 0;
ExecuteEventArgs args = new ExecuteEventArgs(GetSelectedItem());
EventHandler<ExecuteEventArgs> temp = ItemExecuted;
if (temp != null)
{
temp(this, args);
if (args.Cancel) cancel = 1;
}
return cancel;
}
private string GetSelectedItem()
{
string item = null;
IntPtr pStringBuffer = Marshal.AllocHGlobal(2048);
IntPtr pItemBuffer = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(LVITEM)));
int selectedItemIndex = SendMessage(base.Handle, LVM_GETNEXTITEM, (IntPtr)(-1), (IntPtr)LVNI_SELECTED).ToInt32();
if (selectedItemIndex > -1)
{
LVITEM lvi = new LVITEM();
lvi.cchTextMax = 1024;
lvi.pszText = pStringBuffer;
Marshal.StructureToPtr(lvi, pItemBuffer, false);
int numChars = SendMessage(base.Handle, LVM_GETITEMTEXT, (IntPtr)selectedItemIndex, pItemBuffer).ToInt32();
if (numChars > 0)
{
item = Marshal.PtrToStringUni(lvi.pszText, numChars);
}
}
Marshal.FreeHGlobal(pStringBuffer);
Marshal.FreeHGlobal(pItemBuffer);
return item;
}
struct LVITEM
{
public int mask;
public int iItem;
public int iSubItem;
public int state;
public int stateMask;
public IntPtr pszText;
public int cchTextMax;
public int iImage;
public IntPtr lParam;
public int iIndent;
public int iGroupId;
int cColumns; // tile view columns
public IntPtr puColumns;
public IntPtr piColFmt;
public int iGroup;
}
}
public class ExecuteEventArgs : EventArgs
{
public string SelectedItem { get; private set; }
public bool Cancel { get; set; }
internal ExecuteEventArgs(string selectedItem)
{
SelectedItem = selectedItem;
}
}
This should give you an idea of what you would need to do. If you want more than fairly simple events you may want to look for a alternative control, though from what I have seen in the free and low cost areas there are some pretty decent controls but they all have some quirks and will not give a seamless explorer experience.
Remember this code was put together fairly quickly without error handling or comments and ignoring several issues such as multiple selected items, so use it as a guideline and at your own risk.
In order to handle renaming, deleting and make other customization you need to write your own file explorer. WebBrowser control is not suitable for your needs. It's just a wrapper over ActiveX component.
You should check this codeproject article. It contains an implementation of file explorer. There are few more samples of file browser:
one
two
LogicNP Software has two controls (FileView and ShComboBox) that do what your looking for:
http://www.ssware.com/fldrview.htm
You can download a trial from their page, however it's ~130$ for the license.
I have written a library that might be able to help you. You can find it at: http://gong-shell.sourceforge.net/
The control you're looking for is the ShellView. There's tutorials there on how to create a simple Windows Explorer clone in only a few lines too.
Note for .NET 4.0 users: Gong-shell is currently broken for 4.0. The framework introduced changes in Interop and it will build just fine but cause different issues when interfacing with shell32 (notably the shellicon api, leading to an unmanaged null pointer dereference).
Check out this article here, it shows how to do this in .NET and WinForms. Doing it this way gives full-control over what the user sees.
I've used it in one of my applications and it works really well. You can show icon/details/list view and it stops the user moving to other directories (which is often the problem of showing the standard file/directory dialogs.
I use it to show the screen like the one below below http://img7.imageshack.us/img7/7647/screenshotbaf.png:
You may want to look at the ExplorerBrowser object.
See http://blogs.msdn.com/ieinternals/archive/2009/12/30/Windows-7-Web-Browser-Control-will-not-browse-file-system.aspx for more details.
If you are happy being Windows Vista only and wrapping a COM control, IExplorerBrowser might be acceptable for you needs.
This The Code Project article shows its use within an MFC program but at least one other person seems to have got it to work in C# after some effort.
The newer API exposes considerably more programmability than simply intercepting messages, but it is (obviously) useless for older platforms.
If you want to open a different window to display the target folder's content you can use System.Windows.Forms.OpenFileDialog, or SaveFileDialog, or inherit from FileDialog and extend it.
To allow the user to select a folder you can use FolderBrowserDialog, though as a user I don't like that control.
Does this help or you absolutely have to embed a control in your form?
Asaf

Categories