In short, the OP seemed to expect that there should be some special way
of dealing with splash screens, and I was merely seeking to show that
they, well, aren't. I had to confirm this for myself, though, since I
don't do WinForm programming.
Anything else in the examples is pretty much irrelevant. I assume that
the OP knows how to use a "regular" window, and so I assume also that
they know how to interact with it. Being a C# group and not a WinForms
or Windows GUI group, I think that's a reasonable expectation. Anyway,
more below, though it's probably firmly in the "off topic" category
since this is a C# group, and not a Win32, *nix, or GUI programming
group.
On Fri, 17 Apr 2009 16:58:42 -0700, Michael B. Trausch
[...]
That seems to be the approach I am able to find anyway:
http://www.programmersheaven.com/2/FAQ-WinForm-Splash-Screen
This is the most basic implementation of a splash screen one might
come up with. It has the unfortunate flaw that aside from getting
the splash screen to display once, there is no message pump to
allow for further GUI interactions. For example, if the splash
screen, or any other part of the UI, winds up needing to be
redrawn (e.g. someone drags a window over it), it won't be able to.
... add code to do that?
I don't know what you mean. You can always add code to "that",
whatever you mean by "that".
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?
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.
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.
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.
I don't know what "GTK" is supposed to refer to. However, if it's
some kind of library, I suppose it's possible that it abstracts away
the thread affinity aspects that are inherent in Windows for GUI
objects (and which are inherent in many other GUI APIs, including
Java, and both Carbon and Cocoa on the Mac). The .NET APIs, both
Forms and WPF, are "closer to the metal", and expose the same thread
affinity issues that are present at the lower level.
GTK is a portable widget library, originally from the world of
UNIX-like systems. It's the basis of the GNOME desktop environment
and the GIMP graphics program. It's bindings for the CLR are called
GTK#, and are portable to UNIX-like systems, Windows, and OS X, as well
as anything that has a reasonably POSIX-complaint environment and X11.
As far as "repainting comes for free", that's not true in any API.
I'm sure it's not true even in "GTK". But, in all of the major
APIs, repainting is trivially handled by providing some kind of
responder. In the native API, that means handling the WM_PAINT
message. In .NET Forms, that means overriding OnPaint() or handling
the Paint event. Similar mechanisms exist in the other APIs. It's
about as close to "free" as you'll get.
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).
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.
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.
The GUI object responds to various system events, including
repainting and user input, through this message pumping loop. The
loop retrieves a message from the message queue (aka "event queue",
"signal queue", etc. depending on the actual platform) and dispatches
it to the object. Until the object returns from handling that
message, no other messages can be processed.
This all happens on the thread where the GUI object was created.
The message queue is per-thread, and the queue is selected according
to what thread was used to create the GUI object. A message pumping
loop running on thread A will never see messages for objects created
on thread B.
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? 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.
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. 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.
Implications of those points are:
-- If you create an object on the wrong thread, one without a
message pumping loop, then no messages will ever be dispatched to
that object, including those for repainting and user input
-- Until you return from a message handler, no other messages
for ANY object on that thread can be dispatched, including those for
repainting and user input
Failure to note both of those implications can result in a UI that
appears to be "stuck" and unresponsive.
Right.
Even "GTK" is going to be subject to these limitations. If it does
abstract them away from you the user, it would have to do so by using
a different thread for each message dispatched to a GUI object (to
avoid blocking the message pumping loop), and by funneling all of the
GUI operations to a specific thread, to ensure objects are able to
receive and process messages sent to them.
GTK handles GDK events and GTK events. GDK handles events from the
underlying window system (e.g., Cocoa, X11, or Win32) and its own
events (which are called "signals", and which native events are
communicated as to GTK). GDK is built on top of GLib, which provides a
portable main loop and signal passing mechanism that isn't terribly
difficult to use. 90% of the things you'll do there you do as either
call backs or when there is idle time; anything that takes up a
noticeable amount of time, you either block for a bit (not recommended)
or spawn a thread to do the work.
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. 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?)
Whether "GTK" does all that, I don't know. I would be surprised if
it does the former (though it could be done in a reasonably efficient
way by using a thread pool), but it's possible it at least provides
the latter (which, if you're writing a full-blown GUI library that
completely hides the underlying Windows mechanisms used, probably
isn't that much extra effort).
GTK provides its own mechanisms; it doesn't use Win32 widgets on
Windows, though it might use lower-level mechanisms; it's only
dependency on a POSIX system is its own rendering utilities (Cairo,
Pango, FreeType, and various graphics and support libraries for things
like Unicode). However, its own widgets can be themed to look and feel
native. The upshot is that its buttons are handled programmatically
identically on every platform that GTK has been ported to, which is a
great number of them.
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.
That said, what I understand of WinForms is that it is a framework for
Windows application development on top of the CLR, just as GTK and its
associated components provide a framework for more-or-less portable
application development on top of the CLR. Both types of programs can
use the base class libraries, and both implementations, while for
similar purposes, have very different implementations and thus APIs. I
think you made the assumption that GTK is an abstraction of Win32; it
is not---it's a complete framework that sits on top of a base low-level
system. An application wouldn't use both at the same time (at least,
not wisely) since there would be two entirely different main loops
running, performing different things. While WinForms is (somewhat)
portable because other CLR implementations provide versions of its API
with varying levels of compatibility, it's not as portable as GTK,
within or outside of the CLR.
Of course it does. I've provided a number of specific examples and
pointers in my previous reply to your post.
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. Maybe "raise"
would be more appropriate for signals, but that terminology is also
used for exceptions in various environments; "handle" may be a
better term also for signals, but exceptions are handled as well. There
is a certain language barrier to be sure when crossing from platform to
platform or one development environment or framework to another, I
suppose.
It sounds like you're saying that a dialog in WinForms has its own
version of a main loop. 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 any case, I don't ever really use example code; I study it and think
about it when I need to refer to it, and then I turn around and
implement something that fits with what I need, which very often only
borrows tiny bits of idea from the original example. I expect others
(perhaps in err) to think the same of examples.
I don't think programming is a copy and paste sort of thing,
myself---and so citing example is, IMHO, only a beginning of something,
and never is it something I'd turn around and use unless I understood
it well and know exactly why I'd want to use it verbatim. That is of
course due to the sheer number of examples out there that are far less
than adequate for usage in anything serious. 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.
--- Mike