Re: Splash Screens , how could something so basic still be hard?
- From: "Peter Duniho" <NpOeStPeAdM@xxxxxxxxxxxxxxxx>
- Date: Sat, 18 Apr 2009 03:04:21 -0700
On Sat, 18 Apr 2009 01:02:06 -0700, Michael B. Trausch <mbt@xxxxxxxxxxxxxxx> wrote:
[...]
You say that there "is no message pump to allow for further GUI
interactions". What does that mean? Can you not create a form and
handle events in the application's startup routine to trigger updates
to it? If so, that doesn't make sense to me. Maybe the better
question is, how and why would you create and display a form without
having a main loop to do the initial painting of it and subsequent
management of it?
In a good program, you wouldn't. But the examples you offered both do exactly that.
Note that by "there is no message pump", I mean that because of the way the code is written, no message pump is working while the "splash screen" is displayed. In a normal Forms app, there would be a message pump loop, and there's no reason to believe that in those examples, one doesn't exist. The point is that the code given doesn't return until the splash screen is no longer needed, meaning that the message pump loop doesn't get to do any more work until then.
There is effectively no message pump loop for the duration of the splash screen.
> That's what you're supposed to do with GUI
> components is hook into them and fit them to your needs.
Again, not sure what your point is here. There's no well-defined
meaning for "hook into them". All of the .NET GUI classes provide
some kind of mechanism for customization, and all mechanisms
necessarily have their limits.
None of that seems relevant to your question; my point is that the
example given doesn't answer any of the questions YOU appear to
have. As such, it's not that relevant a code sample in terms of the
question you asked.
I was wondering why the OP expected a splash to be any different from a
window in the general sense.
I take it that the OP doesn't really understand how "a window in the general sense" works. These kinds of questions are fairly fundamental, and you can't get very far doing GUI work without having a handle on the concepts. So, someone unfamiliar with the concepts is almost always necessarily new to doing GUI work, at least in this context.
Those were just the first two examples (of thousands) that I found; I
was pointing the OP at them to prod more searching to find better
answers, which is what I'd expect to do when I come across a problem.
At the very least, they show that you just use a regular window, which
is what I thought should have been the case in the first place; that's
how its done elsewhere that I know of.
I believe that the OP understands he wants "a regular window". The problem, as near as I can tell, is that he doesn't understand the correct way to use "a regular window" in the context of doing some other work at the same time.
Hooking in, of course, assumes that you have some sort of main loop
running and processing things for you. Obviously this means that
you're going to be working (at a lower level) with callbacks, and at a
higher level with signals and/or events of some form or another. In
GTK, for example, everything is a signal. You get focus, you get a
signal; you lose it, the same. You get a click, that's a signal, too.
And for all of the above, the main loop has to be running or you can't
even be initially painted.
Right...as with Windows/Forms/WPF/etc. too.
[...] GTK handles the details for exposure events and the like in its main
loop implementation. You don't handle any of that unless you're
creating purely custom widgets, and then of course you must. Widgets
take care of themselves in GTK, which is built on top of GDK, which
itself can run on Cocoa, X11, or Win32. As long as the main loop is
running, you will be repainted. If you have to do something in the
main thread that will take a long time, that means calling the main
loop at intervals to handle those things, or you'll look dead (e.g.,
not responding to a window ping or not accepting keyboard or mouse
input).
One hopes that even in GTK there is a better solution than "calling the main loop at intervals". The same technique is possible in Windows, both unmanaged and managed. But it's a very poor technique, for a variety of reasons. The most noticeable reason from the user's point of view is that the UI still winds up hung for periods of time, unless the message pump is executed extremely often (every few ms, for example). And if you do it that way, your thread spends most of its time checking the message pump instead of getting real work done.
The superior approach is to put the lengthy processing into a different thread, and let the main GUI thread run its message pump unimpeded.
Once you've correctly installed a paint handler according to the API,
then the object will receive the paint "signal" any time it's
required. But, for this to happen, _something_ somewhere needs to
respond to the OS and forward that information to the object. In
Windows, that means a thread has to be in a message pumping loop, and
the way that works in .NET for the Forms objects is to call
Application.Run() (which the default template places in the Main()
method of Program.cs).
So, all that's required, if I understand you correctly, is to so some
very fast and early init, set up the splash screen form, enter the main
loop, and then have the form kick off the rest of the init that it
needs. That's exactly what one would do in GTK, anyway. The
implementation details are pretty well irrelevant---you could use a
single thread and call iterations of the main loop (assuming that
WinForms permits you do to that), or you can have a thread that the
splash screen spawns to handle its work and just take communication for
it. You could even use a design wherein you have a named pipe and a
sibling or child process that handles some of the work, and you get
data on progress over the named pipe, if you have another process doing
cache management or something for you. Depends on the requirements of
the particular implementation.
Of course. You can do all of the above in Windows and/or .NET if you like. Though, spawning a new process and using IPC seems like overkill to me, and processes are so heavy-weight on Windows there'd have to be some really compelling reason to do it (e.g. you're dealing with an .exe you can't change).
That said, a splash screen isn't generally any more complex than a
raster image of some sort, possibly one or more labels, and possibly
one or more other stock widgets used to signify progress, maybe another
label or a progress bar or an animated graphic for something that is
indefinite and doesn't map well to percentages. I don't see why you'd
have to set a paint handler at all; standard widgets should be managing
their painting all on their own.
Every window has a paint handler _somewhere_. But, I don't know where you got the impression that anyone suggested a splash/progress screen should have a custom handler in addition to what's already provided by .NET. There's nothing I wrote that should be taken that way. What's important is to understand that the paint handler _always_ exists, and _always_ needs to be called at the appropriate time via the OS event messaging. If code interferes with that -- for example, by not returning promptly during the handling of an event -- then redrawing doesn't happen.
[...]So, there are two important design points here:
-- A GUI object is tied to the thread on which it was created,
in a fundamental way; interactions with the object have to happen on
that thread -- Code that responds to the various messages that are
sent to a GUI object must return in a timely manner, to allow the
message pumping loop to continue dispatching messages
Interesting. So, if GUI objects are tied to threads that they are
created in, the obvious solution is to have a GUI thread and other
threads that do work, with idle-time calls that check to see if the
work is actually done or check up on progress to be able to perform
updates, no?
You understand it halfway. There should not be "idle-time calls" for checking if "the work is done" or to "check up on progress". That's polling, and polling is to be avoided whenever possible.
That's the whole point of a mechanism like BackgroundWorker, which formalizes the signaling behavior of dealing with progress or work-done events. Even without BackgroundWorker, it's not hard to implement an interrupt-driven system for handling those events, but BackgroundWorker wraps everything up nicely.
Or, expose an event or signal that can be called from the
spawned thread to actually inform the splash to update itself? These
would be how I would think to handle a splash in my own environment.
The "event or signal" in this case is simply the act of executing some appropriate code on the appropriate thread. Again, that's what BackgroundWorker does on your behalf. You subscribe the appropriate code to the appropriate event on the BackgroundWorker, and then that event handler is called, on the appropriate thread, at the appropriate time.
That said, the binding of widgets to threads sounds a bit strange to
me. If you're working with widgets, they should be accessible to
anything that is running in the main thread, assuming that a reference
to them can be obtained.
I don't understand what you mean. Assuming a "widget" is created on the main thread, where the message loop is running, why _wouldn't_ you be able to access them from the main thread?
Unless Windows separates threads into
different address spaces, I don't see how you'd be unable to reference
things in different threads. You just have to do so carefully.
It simply has to do with the way Microsoft designed .NET. Ironically, cross-thread access is actually simpler in some ways when using the unmanaged API than when using the managed API. In particular, in the unmanaged API, practically everything you do with a window (the basic UI object, on which all actual windows and controls are built) is done through messages. And the two message-delivery functions, SendMessage() and PostMessage(), both handle the cross-thread issues for you.
In .NET, it's more explicit...the underlying messaging is wrapped by .NET, so you don't notice it when you're doing things with a control (e.g. calling a method or setting/getting a property). But when you try to do something like that from the wrong thread, you have to use the marshaling mechanisms available.
It's not so much that you can't access the memory addresses in use (though with thread-local storage, that's certainly a possibility). It's just that if you don't do things on the right thread, the data structures aren't reliable. Frankly, it's worse than a simple memory access problem, which would throw an exception as soon as you did it wrong. Instead, you get subtle errors that may show up long after the code that caused the problem executed.
[...]
If you frequently use threads for certain repetitive purposes, then
your application will usually contain threads that stay alive
throughout the program that handle these tasks.
In .NET, the ThreadPool generally deals with that sort of thing. In fact, BackgroundWorker uses the ThreadPool for the actual execution of the DoWork event handler.
Mostly, this means
that you use a thread for I/O, and a thread that handles most
everything else, which runs the main loop. If you write a
single-threaded application, then you have to take additional steps to
ensure that you remain responsive. Those would include asking the
operating system to read data, and then polling for that data during
idle time in the main loop using a select() on the file descriptors.
(Does Win32 provide for such a thing?)
All complex operatings systems I've run into have those kinds of mechanisms, including Windows. Possibly due to the limitations of using something like "select()" on Windows, Windows also has other mechanisms for dealing with i/o, including asynchronous callbacks and i/o completion ports.
That said, even on Unix I would not poll in my GUI thread to handle i/o. I'd create a separate thread, and let it block on select(), letting that dedicated thread deal with the i/o as appropriate.
In .NET, the i/o classes all have an asynchronous pattern that can be used with them, that uses the i/o completion port thread pool where applicable, and the regular thread pool if not. IMHO, this is a far superior approach than either polling from the main GUI thread (which is awful) or dedicating a worker thread (preferably pooled) to each i/o operation (which is not so bad, but doesn't scale very well).
GTK provides its own mechanisms; it doesn't use Win32 widgets on
Windows, though it might use lower-level mechanisms;
You'll have to define "widget". There's not literally any such thing in the Win32 unmanaged API (or in .NET, for that matter), though there are certainly objects in each API that meet the usual description of a "GUI widget". And there's no "lower-level mechanism" in the unmanaged API than the basic HWND window handle (unless the library is looking to replace USER.DLL altogether, which is an insane thing to do). It's certainly possible that it uses only custom window classes, rather than the ones built into Windows, but even there, it's using the basic windowing mechanism in Windows.
[...]In any case, none of this is particularly challenging or inefficient
in .NET. You just have to know what you're doing. Until you do,
IMHO you should avoid jumping to conclusions and what is and what is
not "as easy to do with WinForms" as it is in some other library
you're used to using.
I'll wholeheartedly admit that I know very little about WinForms; I
simply don't use it because I only write and work on software that is
portable in nature, and I use GTK for GUIs (and most often, write
event-driven but non-GUI software). That said, I have worked with GTK
both within and outside of the CLR, and find it to be pretty
straightforward and easy to use.
I have not said anything to the contrary. I'm saying that I doubt the Windows API (whichever part of it someone chooses to use) is much different.
[...] I
think you made the assumption that GTK is an abstraction of Win32;
You thought wrong. But, you would be incorrect to assume that GTK doesn't use the Win32 API at all. Win32 _is_ the "base low-level system" on Windows. The only way to be lower is to be writing the drivers.
[...]> Can't you throw
> events or signals or something from one thread and catch them in
> another?
You should avoid using the words "throw" and "catch", since those
imply exception handling, which is a completely different issue. But
otherwise, yes...as I've said, you not only can marshal GUI handling
from one thread to another, it's essential that you do so.
Don't know why you're talking about marshaling anything.
Because that's the word we use to talk about moving data from one context to another.
Maybe "raise"
would be more appropriate for signals, but that terminology is also
used for exceptions in various environments;
To some degree in Win32, and to a very strong degree in .NET, "throw" is _the_ acceptable term for generating an exception. That's why C# uses the word "throw" and not "raise".
"handle" may be a
better term also for signals, but exceptions are handled as well.
I didn't say anything about "handle"...I commented on your use of the word "throw", which is absolutely not used for events in .NET. In fact, you'll note that I did use the phrase "exception handling", so obviously I'm not saying "handle" is contrary to contexts involving exceptions, even as the same word is used when dealing with events.
[...]
It sounds like you're saying that a dialog in WinForms has its own
version of a main loop.
Yes, it does.
That doesn't make sense to me; while you _can_
have more than one main loop, I am not sure why you'd want to, it
sounds like unnecessary complexity to me. It seems like you'd just
want to handle the dialog with its own set of callbacks that get
signaled from the main loop in the usual fashion.
In this context, "dialog" refers to a modal window. It has its own message loop so that it can intercept user input that might be bound for some other window, but which should be blocked in a modal state.
In the unmanaged API, one sometimes refers to a particular kind of "stay-on-top" window as a "modeless dialog", and that kind of window doesn't have its own message loop. But in .NET, because the method named "ShowDialog()" always creates a modal window, I've tended to shy away from using "dialog" to describe anything but a modal window.
[...] Examples are good for a
high-level overview, but of course that high-level overview is
meaningless if you don't have any idea as a programmer what you're
doing. I assume that a programmer can take from any example exactly
what they need and nothing more.
Um. Well, then...what "high-level overview" is it that the examples you offered can provide to the OP that would beneficial and useful in answering his question?
Because, as I stated at the outset, there's not really anything there that is directly applicable to the particular question he had (a question which wasn't so much about how to use a window to make a splash screen, but rather how to coordinate said splash screen with code that's actually doing real work).
Pete
.
- References:
- Splash Screens , how could something so basic still be hard?
- From: RvGrah
- Re: Splash Screens , how could something so basic still be hard?
- From: Michael B. Trausch
- Re: Splash Screens , how could something so basic still be hard?
- From: Peter Duniho
- Re: Splash Screens , how could something so basic still be hard?
- From: Michael B. Trausch
- Re: Splash Screens , how could something so basic still be hard?
- From: Peter Duniho
- Re: Splash Screens , how could something so basic still be hard?
- From: Michael B. Trausch
- Splash Screens , how could something so basic still be hard?
- Prev by Date: Re: Entity Splitting question
- Next by Date: capture keystroke before display in textbox
- Previous by thread: Re: Splash Screens , how could something so basic still be hard?
- Next by thread: Re: Splash Screens , how could something so basic still be hard?
- Index(es):
Relevant Pages
|