MJW Posted June 17, 2015 Author Share Posted June 17, 2015 (edited) I admit, I care. I didn't know that's what the help button was meant to do. I guess I haven't seen that many windows with the question mark button. I did notice the question mark cursor, which setting the event arg's Cancel undoes. I still may use it, assuming there isn't some hidden problem. (Which there might well be with something that seems so wacky.) (What's up with the comment editor? At least for me, there seems to be no line wrapping.) Edited June 17, 2015 by MJW Quote Link to comment Share on other sites More sharing options...
midora Posted June 17, 2015 Share Posted June 17, 2015 Even the most users do not known, so there are no special expectations ;-) You still may have to add a hint somewhere that there is a clickable button in the caption. It's a mess that this button is not available if the form has a min or maxbox. Next step would be to add an additional help button to the bottom line of the dialog. But I guess Rick wouldn't be happy with this ;-) Quote Link to comment Share on other sites More sharing options...
BoltBait Posted June 17, 2015 Share Posted June 17, 2015 MJW, I think your solution is brilliant! I added a couple of screenshots to your post. I guess Rick wouldn't be happy with this ;-)Yeah, I'd like Rick's blessing before using this technique myself as he did post the following in this forum's rules: Plugins must not modify the Paint.NET user interface. There are non-Reflection methods for doing this. Plugins are not allowed to modify Paint.NET's UI in any way such as by adding menu commands, etc. Quote Click to play: Download: BoltBait's Plugin Pack | CodeLab | and how about a Computer Dominos Game Link to comment Share on other sites More sharing options...
Rick Brewster Posted June 17, 2015 Share Posted June 17, 2015 It's a bad idea to rely on a Form's Name equalling anything unless you own that Form. Who knows, in a future version of Paint.NET I may need to use that for something. In fact, Form.ActiveForm isn't even guaranteed to give you what you want -- it could be null if the active "form" is actually a non-WinForms window (like an Open/Save dialog, or a MessageBox). So you can't really rely on it for the BeginInvoke() trampoline either. And, Application.OpenForms is going to have race conditions ... I was thinking you could use Application.OpenForms.First().BeginInvoke() but that just feels like another slimey hack. (These might actually work fine in practice for the scenario y'all are trying to solve right now, but it's not the right general solution and I'd like to avoid one of these techniques being propagated to other coding projects and stuff) WinForms doesn't seem to have a proper way to send a callback (e.g. Invoke) to the "main" thread unless you already have a reference to a UI control from that thread. That's lame, but let's deal with it: I'll give official blessing to use something called PdnSynchronizationContext (at the moment I can't recall if it's in Base.dll or Core.dll), which is what Paint.NET uses for doing exactly what we need here. It's derived from .NET's SynchronizationContext, but you can't just use SynchronizationContext.Current because that's a per-thread value. Instead, use PdnSynchronizationContext.Instance, which returns the singleton PdnSynchronizationContext that's parked on the main thread. (If you're already on the main thread, then SynchronizationContext.Current == PdnSynchronizationContext.Instance, but no need to worry about that). using PaintDotNet.Threading; void Render(...) { ... PdnSynchronizationContext.Instance.Send(new delegate { // congratulations, you're now on the UI thread. go ahead and show your message box! }); ... } Note that SynchronizationContext uses the lingo of Send and Post instead of Invoke and BeginInvoke. They are equivalent, and quite often are used for wrapping the other. Send() and Invoke() are synchronous and will not return until your callback is finished, whereas Post() / BeginInvoke() are asynchronous. SynchronizationContext is a lower level component and is just reusing the lingo from Win32's SendMessage and PostMessage. Send() and Post() also accept a second parameter called "state" of type Object. You don't need to care about this probably, but you can use it to pass along information to your callback. It's not really necessary in most cases because of the way .NET does closures. Notice how I used the anonymous delegate syntax without a parameter list? That's the syntactic form that says "well, the delegate type that's needed here has parameters but I don't care about them." The Send() method in use above is also just an extension method that I added so I would have to constantly put 'null' for the state parameter. Quote The Paint.NET Blog: https://blog.getpaint.net/ Donations are always appreciated! https://www.getpaint.net/donate.html Link to comment Share on other sites More sharing options...
MJW Posted June 17, 2015 Author Share Posted June 17, 2015 (edited) Later on, I'll try to figure out to do this within Rick's rules (or someone else is welcome to do so). But purely for don't-try-this-at-home entertainment: // Author: MJW // Name: Test Help Menu // Title: Test Help Menu // Desc: Try to add a help meu item. #region UICode byte Amount1 = 0; // [255] Randomize #endregion bool hasHelpMenu = false; Form form = null; void Render(Surface dst, Surface src, Rectangle rect) { if (!hasHelpMenu) // Help menu displayed? { hasHelpMenu = true; form = Form.ActiveForm; if (form.Name == "EffectConfigDialog") { form.Invoke(new Action(delegate() { AddHelpMenu(); } )); } } ColorBgra CurrentPixel; for (int y = rect.Top; y < rect.Bottom; y++) { if (IsCancelRequested) return; for (int x = rect.Left; x < rect.Right; x++) { CurrentPixel = src[x,y]; CurrentPixel.R = (byte)RandomNumber.Next(255); CurrentPixel.G = (byte)RandomNumber.Next(255); CurrentPixel.B = (byte)RandomNumber.Next(255); CurrentPixel.A = (byte)255; dst[x,y] = CurrentPixel; } } } private void AddHelpMenu() { if (form.Menu != null) return; MenuItem menuItemHelp = new MenuItem("&Help"); MainMenu mainMenu = new MainMenu(); mainMenu.MenuItems.Add(menuItemHelp); menuItemHelp.Click += new System.EventHandler(ShowHelpMessage); form.Menu = mainMenu; form.Height += 20; form.PerformLayout(); } public void ShowHelpMessage(Object sender, EventArgs e) { System.Windows.Forms.MessageBox.Show("This is the help text"); } Edited June 17, 2015 by MJW Quote Link to comment Share on other sites More sharing options...
MJW Posted June 17, 2015 Author Share Posted June 17, 2015 (edited) I don't really see what's wrong with using the parent's menu name. The idea is aimed exclusively at CodeLab plugins, and only when the dialog is being displayed. The name test is intended to avoid problems with inadvertently changing another form. I'd rather have the code not run when I'd want it to then run when I don't want it to. Edited June 17, 2015 by MJW Quote Link to comment Share on other sites More sharing options...
BoltBait Posted June 17, 2015 Share Posted June 17, 2015 MJW, sorry, but I don't like the look of adding a menu to the IndirectUI. Let's get some code working based on your idea of adding the ? button with Rick's code. I'd do it myself, but I'm on my phone. BTW, Rick, I thought delegate required () after it. Quote Click to play: Download: BoltBait's Plugin Pack | CodeLab | and how about a Computer Dominos Game Link to comment Share on other sites More sharing options...
MJW Posted June 18, 2015 Author Share Posted June 18, 2015 I was just sort of showing off how tricky I could get with my new Invoke (and, I hope, PdnSynchronizationContext) toy. Quote Link to comment Share on other sites More sharing options...
MJW Posted June 18, 2015 Author Share Posted June 18, 2015 I'll try making a PdnSynchronizationContext version tonight, unless someone beats me to it. Quote Link to comment Share on other sites More sharing options...
null54 Posted June 18, 2015 Share Posted June 18, 2015 The following uses PdnSynchronizationContext. Note that it works in Paint.NET 4.0 only. #region UICode byte Amount1 = 0; // [255] Help byte Amount2 = 0; // [255] Randomize #endregion int PreviousHelpButton = -1; void Render(Surface dst, Surface src, Rectangle rect) { if (PreviousHelpButton== -1) // Initial run? { PreviousHelpButton = Amount1; // Don't show help } if (PreviousHelpButton != Amount1) // Help button pressed? { PreviousHelpButton = Amount1; // Reset help button PaintDotNet.Threading.PdnSynchronizationContext.Instance.Send(new System.Threading.SendOrPostCallback(delegate(object state) { // This line runs on the UI thread and not on the Render thread System.Windows.Forms.MessageBox.Show("This is the help text"); }), null); } ColorBgra CurrentPixel; for (int y = rect.Top; y < rect.Bottom; y++) { if (IsCancelRequested) return; for (int x = rect.Left; x < rect.Right; x++) { CurrentPixel = src[x,y]; CurrentPixel.R = (byte)RandomNumber.Next(255); CurrentPixel.G = (byte)RandomNumber.Next(255); CurrentPixel.B = (byte)RandomNumber.Next(255); CurrentPixel.A = (byte)255; dst[x,y] = CurrentPixel; } } } Quote Plugin Pack | PSFilterPdn | Content Aware Fill | G'MIC | Paint Shop Pro Filetype | RAW Filetype | WebP Filetype The small increase in performance you get coding in C++ over C# is hardly enough to offset the headache of coding in the C++ language. ~BoltBait Link to comment Share on other sites More sharing options...
MJW Posted June 18, 2015 Author Share Posted June 18, 2015 Thanks, null54, that will certainly help. Looks like the change from Invoke to PdnSynchronizationContext is pretty direct (as Rick suggested it was). Is the PdnSynchronizationContext not available prior to 4.0, or will it just not work correctly? Quote Link to comment Share on other sites More sharing options...
null54 Posted June 18, 2015 Share Posted June 18, 2015 Is the PdnSynchronizationContext not available prior to 4.0, or will it just not work correctly? It is located in PaintDotNet.Base.dll in 4.0, it is not available in 3.5.11. Quote Plugin Pack | PSFilterPdn | Content Aware Fill | G'MIC | Paint Shop Pro Filetype | RAW Filetype | WebP Filetype The small increase in performance you get coding in C++ over C# is hardly enough to offset the headache of coding in the C++ language. ~BoltBait Link to comment Share on other sites More sharing options...
MJW Posted June 18, 2015 Author Share Posted June 18, 2015 Here is my Help Button version; // Author: MJW // Name: Test PDN Help Button // Title: Test PDN Help Button // Desc: Add a PDN-approved help button. #region UICode byte Amount1 = 0; // [255] Randomize #endregion bool hasHelpButton = false; void Render(Surface dst, Surface src, Rectangle rect) { if (!hasHelpButton) // Help button displayed? { hasHelpButton = true; PaintDotNet.Threading.PdnSynchronizationContext.Instance.Send(new System.Threading.SendOrPostCallback(AddHelpButton), null); } ColorBgra CurrentPixel; for (int y = rect.Top; y < rect.Bottom; y++) { if (IsCancelRequested) return; for (int x = rect.Left; x < rect.Right; x++) { CurrentPixel = src[x,y]; CurrentPixel.R = (byte)RandomNumber.Next(255); CurrentPixel.G = (byte)RandomNumber.Next(255); CurrentPixel.B = (byte)RandomNumber.Next(255); CurrentPixel.A = (byte)255; dst[x,y] = CurrentPixel; } } } void AddHelpButton(object state) { // Get the parent form. FormCollection forms = System.Windows.Forms.Application.OpenForms; Form form = forms[forms.Count - 1]; if (!form.HelpButton) { form.HelpButton = true; form.HelpButtonClicked += new System.ComponentModel.CancelEventHandler(HelpButtonClicked); } } public void HelpButtonClicked(Object sender, System.ComponentModel.CancelEventArgs e) { e.Cancel = true; System.Windows.Forms.MessageBox.Show("This is the help text"); } There may be a better way to get the parent's form. I don't even know if the method I used always works. Rick didn't seem to like Form.ActiveForm, so I tried to find an alternative. Quote Link to comment Share on other sites More sharing options...
Rick Brewster Posted June 18, 2015 Share Posted June 18, 2015 Do not add UI to the effect dialog like that. Quote The Paint.NET Blog: https://blog.getpaint.net/ Donations are always appreciated! https://www.getpaint.net/donate.html Link to comment Share on other sites More sharing options...
MJW Posted June 18, 2015 Author Share Posted June 18, 2015 Like what, particularly? If you mean the method by which I add the Help Button, I know of no other way, though I'd be happy to learn. If you mean don't add the Help Button, I'll, of course, abide by your decision, even though I won't like it and I don't see the reason for it. Other than replacing Invoke with PdnSynchronizationContext, it's essentially the same code you commented on previously, without particular disapproval. I can understand not liking the menu-bar version, which was mostly done as a lark, anyway, but the Help Button version seems fairly benign, and is a lot prettier than borrowing the reseed button. Quote Link to comment Share on other sites More sharing options...
midora Posted June 18, 2015 Share Posted June 18, 2015 Like what, particularly? If you mean the method by which I add the Help Button, I know of no other way, though I'd be happy to learn. If you mean don't add the Help Button, I'll, of course, abide by your decision, even though I won't like it and I don't see the reason for it. But the reason is simple. We are back to what we said at the start of this thread. CodeLab maps to PropertyBased effects. And if you bypass this API then there is always the risk that something is not working in the future. Quote Link to comment Share on other sites More sharing options...
BoltBait Posted June 18, 2015 Share Posted June 18, 2015 Do not add UI to the effect dialog like that. Like what, particularly? MJW, I think it all comes down to the rules of this forum where Rick states: Plugins must not modify the Paint.NET user interface. There are non-Reflection methods for doing this. Plugins are not allowed to modify Paint.NET's UI in any way such as by adding menu commands, etc. As for this... the Help Button version seems fairly benign, and is a lot prettier than borrowing the reseed button. I agree, 100% Rick, throw us a bone here! Let us use this: // Name: Help Button Demo [?] #region UICode byte Amount1 = 0; // [255] Randomize #endregion string HelpText = "This is the help text.\r\n\r\nMore Help here."; //string HelpText = "http://www.BoltBait.com/pdn"; void Render(Surface dst, Surface src, Rectangle rect) { Form IndirectUIForm = Form.ActiveForm; if (IndirectUIForm != null) { if (IndirectUIForm.Name == "EffectConfigDialog") { if (!IndirectUIForm.HelpButton) { IndirectUIForm.Invoke(new Action(delegate() { IndirectUIForm.HelpButton = true; IndirectUIForm.HelpButtonClicked += new System.ComponentModel.CancelEventHandler(HelpButtonClicked); })); } } } ColorBgra CurrentPixel; for (int y = rect.Top; y < rect.Bottom; y++) { if (IsCancelRequested) return; for (int x = rect.Left; x < rect.Right; x++) { CurrentPixel = src[x,y]; CurrentPixel.R = (byte)RandomNumber.Next(255); CurrentPixel.G = (byte)RandomNumber.Next(255); CurrentPixel.B = (byte)RandomNumber.Next(255); CurrentPixel.A = (byte)255; dst[x,y] = CurrentPixel; } } } public void HelpButtonClicked(Object sender, System.ComponentModel.CancelEventArgs e) { e.Cancel = true; if (HelpText.ToLower().StartsWith("http://") || HelpText.ToLower().StartsWith("https://")) { System.Diagnostics.Process.Start(HelpText); } else { System.Windows.Forms.MessageBox.Show(HelpText,"Help",MessageBoxButtons.OK,MessageBoxIcon.Information); } } or give us a better way to do the same thing. __________ Quote Click to play: Download: BoltBait's Plugin Pack | CodeLab | and how about a Computer Dominos Game Link to comment Share on other sites More sharing options...
MJW Posted June 18, 2015 Author Share Posted June 18, 2015 Plugins must not modify the Paint.NET user interface. There are non-Reflection methods for doing this. Plugins are not allowed to modify Paint.NET's UI in any way such as by adding menu commands, etc. I don't see this as modifying Paint.NET's UI. It modifies the plugin's UI. I realize it's a fine line when dealing with something as integrated into PDN as the IndirectUI, but there's an important distinction: changes to the effects IndirectUI dialog only affect that effect; once it's completed, nothing in PDN is changed. The PDN interface is the same, and no other plugins or other effects are changed. I'm aware that "if you bypass this API then there is always the risk that something is not working in the future," but that's true of many things. Lots of plugins didn't work when 4.0 came out. I don't see the Help Button change as something that would likely cause major headaches in the future, but if it does, plugins that use it will have to be modified. I added the test for the HelpButton property already set with the idea that someday it might be used by the IndirectUI dialog, and I didn't want to interfere with that use. Obviously the best solution would be to have some help-menu functionality incorporated into the PropertyBased effects, but I hope until that happens, Rick takes pity on us and provides a solution which is acceptable to him. EDIT: One thing I could do to reduce the chance of future incompatibility, is to add the form name check back into my into my PdnSynchronizationContext version. Then if Rick changes the IndirectUI interface to something that will make the help-button trick not work, he could change the dialog name, and the help-button change would have no affect. I could be wrong, but I assume the choice of the dialog's name is pretty arbitrary. If for some reason he changed the dialog name for some other reason, all that would happen is that plugins that used the help-button trick would no longer provide a help-menu option. (I like the form name check anyway, since though I assume the last-run dialog is the one at the end of the application's form list, I don't know it for a fact. I'd intended to look into that, so that I didn't inadvertently modify some other form. The version I posted last night was intended partly as a "proof of concept," and partly to see if someone knows a better way to get the dialog's form.) Quote Link to comment Share on other sites More sharing options...
BoltBait Posted June 18, 2015 Share Posted June 18, 2015 I don't see this as modifying Paint.NET's UI. It modifies the plugin's UI. I realize it's a fine line when dealing with something as integrated into PDN as the IndirectUI, but there's an important distinction: changes to the effects IndirectUI dialog only affect that effect; once it's completed, nothing in PDN is changed. The PDN interface is the same, and no other plugins or other effects are changed. I agree with this. I was just thinking... Code to add the ? button doesn't need to go in the Render function at all. In fact, if I modified CodeLab, I could have it build all the necessary code in other places and the user's CodeLab script could look like this: // Name: Help Button Demo [?] // Help: This is the help text.\r\n\r\nMore Help here. #region UICode byte Amount1 = 0; // [255] Randomize #endregion void Render(Surface dst, Surface src, Rectangle rect) { ColorBgra CurrentPixel; for (int y = rect.Top; y < rect.Bottom; y++) { if (IsCancelRequested) return; for (int x = rect.Left; x < rect.Right; x++) { CurrentPixel = src[x, y]; CurrentPixel.R = (byte)RandomNumber.Next(255); CurrentPixel.G = (byte)RandomNumber.Next(255); CurrentPixel.B = (byte)RandomNumber.Next(255); CurrentPixel.A = (byte)255; dst[x, y] = CurrentPixel; } } }It would be quite simple, really.The help comment could take either of two forms: // Help: which would open a web browser when ? is clicked. Or, // Help: This is the help text.\r\n\r\nMore Help here.which would show the dialog box.EDIT: I just tested it and it works great. I could have CodeLab ready in an hour. All we need now is Rick's blessing. Quote Click to play: Download: BoltBait's Plugin Pack | CodeLab | and how about a Computer Dominos Game Link to comment Share on other sites More sharing options...
MJW Posted June 19, 2015 Author Share Posted June 19, 2015 I certainly like the idea of incorporating the Help function into CodeLab, but the one thing I wish is that the method would allow for longer help text. What I'd prefer is to put the sort of control-by-control description I put with, say, the HSV Eraser plugin, preferably with some degree of text formatting to allow bolding. I know the trend is to make Help open informational websites in a browser, but it isn't a trend I care for. Quote Link to comment Share on other sites More sharing options...
BoltBait Posted June 19, 2015 Share Posted June 19, 2015 What I'm proposing could handle simple help text that could be displayed in a message box* and if you have more complex needs you supply a URL where you have ultimate flexibility. URL's could point to the message board or your own web site. *My demo shows how to make blank lines in a messagebox. You can also use "\t" for a tab character and Alt-255 for a forced space to control word wrap. Quote Click to play: Download: BoltBait's Plugin Pack | CodeLab | and how about a Computer Dominos Game Link to comment Share on other sites More sharing options...
Ego Eram Reputo Posted June 19, 2015 Share Posted June 19, 2015 Or supply the Help file as a PDF and have the URL point to a local version which is downloaded with the plugin. Quote ebook: Mastering Paint.NET | resources: Plugin Index | Stereogram Tut | proud supporter of Codelab plugins: EER's Plugin Pack | Planetoid | StickMan | WhichSymbol+ | Dr Scott's Markup Renderer | CSV Filetype | dwarf horde plugins: Plugin Browser | ShapeMaker Link to comment Share on other sites More sharing options...
Rick Brewster Posted June 19, 2015 Share Posted June 19, 2015 Adding menu items to a Form that you don't own is bad. You may feel reasonably entitled to it since in a way it is "your Form" because it is your plugin, and I can certainly understand that. I'd be OK with using HelpButton and HelpButtonClicked if there was a reliable way to ensure that the Form you're getting is "yours." Inspecting the Name, or even the type (e.g. "if (Form is EffectConfigDialog)"), is both a fragile solution and one that that ties my hands on future architecture choices. It's "fragile" because relying on the Name is circumstantial and weak (I mean "weak" in sort of a mathematical sense there). It's "weak" because any Form can set its Name to that and it'll get caught by your code. Checking based on the type is also circumstantial because it assumes 1) your Effect has a Form, and 2) that it's the only Effect with a Form. I'm happy to add some kind of HelpButton access to IndirectUI in the next Paint.NET update. But, it won't work on previous versions of Paint.NET. And BoltBait will have to update CodeLab for it (AFAIK). Quote The Paint.NET Blog: https://blog.getpaint.net/ Donations are always appreciated! https://www.getpaint.net/donate.html Link to comment Share on other sites More sharing options...
Rick Brewster Posted June 19, 2015 Share Posted June 19, 2015 BTW, Rick, I thought delegate required () after it. delegate doesn't require () in some contexts and is a clean way of ignoring parameters. Technically you can do this with Linq ... IEnumerable<int> someIntList = ... IEnumerable<int> anIntListOfAllSevensThatIsTheSameLengthAsSomeIntList = someIntList.Select(delegate { return 7; }); Although it's more often used in situations like you see with SynchronizationContext.Post/Send. In that case you have a "context" or "state" parameter and it's not always used. Using delegate-without-() is a nice way to document to readers and future maintainers of your code that the parameter isn't used. In my opinion it's better than doing something like "delegate(object ignoredState) { ... }". I don't believe it would work if there's ambiguity as a result. For instance, if the compiler has to choose between a method that takes an Action<T> vs. Action<T1, T2>, then that would be ambiguous., e.g., public static void GimmeYourAction<T>(Action<T> action) { ... } public static void GimmeYourAction<T1, T2>(Action<T1, T2> action) { ... } ... GimmeYourAction(new delegate { return; }); // clearly ambiguous! Quote The Paint.NET Blog: https://blog.getpaint.net/ Donations are always appreciated! https://www.getpaint.net/donate.html Link to comment Share on other sites More sharing options...
MJW Posted June 20, 2015 Author Share Posted June 20, 2015 (edited) The modal Help window is far superior to the pseudo-modeless window that prevents the dialog from closing until it's closed, but better yet would be a modeless Help window that closed when the main dialog was closed. That way, the user could consult the Help menu while using the effect. Best of all, would be probably be allowing the plugin to choose between modal or modeless. Edited June 20, 2015 by MJW Quote Link to comment Share on other sites More sharing options...
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.