I am (still) writing a small application which requires me to use several windows forms to show to the user.
Some of the forms just show progress messages while the application performs tests using several external devices.
The forms will usually be used in order (see below) but there may be some errors picked up from the devices, in which case an Error Reporting form will be used. The user will have the option to go back to the beginning or to the 2nd test (the 1st test takes 30 mins to perform). The error report can be invoked from any other form.
Also, the final form has the option to go back to the beginning to perform the tests on a new device.
Obviously this would cause the suite of forms to get rather tangled up. if it were used for several devices with errors etc.
So am I have a few questions.
Am I using the forms correctly and if so, how do I pass control from one form to the next one without having to go back to the original form, if that makes sense ?
Can I still have access to all variables created in preceding forms, or should I create all the variables in the initial form setup ?
Or should I have all the processing within one parent form and simply "show" the other forms as part of the procedure ?
I hope this doesn't sound too stupid, but I havent used multiple forms in C# yet. The steps are ALSO dependent on each other.
The following is the usual flowchart of forms, with PRGERREP being called from any form (more or less).
PRGSTART
PRGDEFAULT
PRGTEST1
PRGTEST2
PRGTEST3
PRGTEST4
PRGMANUAL
PRGFINALE
PRGERREP
Any help or advice would be most appreciated.
Please try to focus more on the question, not the context.
Even though i don't know exactly what you want, you should check out MDI Applications.
Basically you have a parent form with several child forms. Should your tests be finished you can BringToFront() the corresponding child window from the parent form.
I question the requirement to use multiple forms for this. Most applications show all information for a task; progress, tests, errors, messages, et al. in a single window, not multiple windows. Web browsers and office applications are very complex programs and manage to show all task information in a single window.
The fact that you are trying to "pass control" between multiple forms makes me suspect using multiple windows is a bad design. Look at the windows on your screen now; each one is a self-contained environment and does not need to "pass control" between each other.
If you are trying to make the user do something in sequence, handling errors before going on to the next step and allowing the user to "go back to the beginning", a wizard-like design may be better.
Create a single form with fields and buttons for the first task. Clicking a button redraws the form with fields and buttons for the next task, and buttons to go back or start over. This can be done more easily than creating a bunch of separate forms and trying to synchronize data between them.
Consider inheriting from an ApplicationContext to implement this logic in your application. See here and here.
Related
Problem
I recently replaced a System.Windows.Forms.WebBrowser-based control with one that relies on Microsoft.Web.WebView2.WinForms.WebView2 instead. This has been working well, except that my users want to have 2 instances of their application running at once, with both instances occasionally having that WebView2 form open at the same time. When they try this, one of the two instances fails to load the WebView2 control until the other one is closed.
Learned / attempted so far:
I've found WebView2 to be extremely finicky, so I've looked into alternatives like CefSharp, but haven't wanted to dedicate time to implementing them until I know they can solve the problem. So far as I can tell, CefSharp and WebView2 do very similar things as far as launching a Chromium-based browser, rendering to a WinForms control.
This blog post (read: advertisement) for paid alternative DotNetBrowser indicates that
You can initialize and use several Chromium engines simultaneously with different configurations [in DotNetBrowser], which is not possible in CefSharp.
I'm wondering if this same limitation applies to WebView2, and is the cause of my users' inability to have 2 instances of that same form loaded at once.
Context
I'm supporting an ancient desktop CRM that's set to be retired in favor of a modern alternative, but in the mean time, the old standard needs to integrate with new processes we're bringing in. One of those new processes is a web page from an external service that our call center users enter data into, then expect the CRM to pull entered data out of the web page.
The part of their process I did not know about is the 2-instance bit from above: they're used to being able to copy-and-paste from one embedded browser window to the other.
#Poul Bak in the comment on the question had it right: providing settings that differ per-instance of the application fixed the problem.
The setting I changed per-instance was the path of the user data folder: I was able to pass different values for the userDataFolder argument of the CoreWebView2Environment.CreateAsync method, branching based on a variable that holds different values between the instances that my users are running.
I am a fairly experienced WinForms developer. I have an MdiApplication that used to work well. However, recently the main shell of the application, for which we use ComponentOne RibbonForm, has been updated in a big way. This update did affect some of our other 3rd party components, which we established was due to ComponentOne's use of DoEvents() in their event code. I thought I had cleaned up all of the code causing problems but I now have found another...
When I have multiple MdiChildren open and select one of these in code from an button click event on the ribbon form via
document.Activate();
document.EditorControl.Select();
document.EditorControl.Focus();
the other open MdiChildren documents still have focus, that it the forms are highlighted and input is not set on the document I set in code. Two questions:
How can I ensure that the Form I want to make active is the only one that is active?
Linking to the above; setting one form as active using form.Activate() should deactivate the others MdiChildren, but it is not - how can I deactivate the other windows in code?
Thanks for your time.
[Too long for a comment]
I am sick to the hind teeth with fighting C1. Esp. the Ribbon. I have confirmed with their support that they do use DoEvents() which they use to yield on their Gui threads. I am now going to switch to DevExpress which should be straight forward for my MVC application...
C1's use of DoEvents() messes up the normal flow of your application. DoEvents() is asynchronous which means it terminates before the application has actually processed any outstanding events, so if you're using it in a procedure with many sequential statements, calling DoEvents() causes a huge disturbance whenever it's called. This is what I think we are seeing when we perform our MDI operations, but we can never be sure without the C1 source code.
I hope this helps.
I am working on a application, which would be the face of lot of other tools running in background. I am facing an issue. During the launch of a background application ,it needs to load a specific file(file-->load--> file name).
Let the front end application be Fapp and the background application be Bapp. Is it possible for Fapp to get the handle of Bapp's menu item and trigger the load function. I am able to get the handle for buttons but not able to do the same for menu items.
Now we are achieving this using AutoIt, I am trying to achieve this in C# itself.
After you have obtained the handle of the window that you want to invoke its menus, then you may use
HMENU GetMenu(HWND) windows api to get menu
HMENU GetSubMenu(HMENU, int) to get to the file menu and again to open menu.
BOOL GetMenuItemInfo( ... ) to get info about menu
and you can use PostMessage((IntPtr)hWnd, WM_COMMAND, 0, ID_MENU_ITEM); (related post) to perform a click on that item.
all these apis are what AutoIt calls (I think). This solution works if your Bapp is a normal windows application with a normal windows menu, not a fancy WPF app, or a ribbon. If this is the case, then what you see as menu probably is not a menu (technically anyway)
Are you sure this is the right way to get two applications talking to each other?
If you don't have source code for BApp, and it also doesn't have an API that you can use, then pretending to be an interactive user could be the only way to interact with it. Just be aware that it is fraught with issues, consider what will happen when
BApp isn't already running
BApp has a modal dialog open
BApp is in the middle of an operation (or hanging) and its menu is disabled
BApp is updated to a new version and its UI changes
An interactive user changes focus, in the middle of an operation.
An alternative to this would be to do the same thing that you do when you are unit testing an application with a UI. This is because you are doing the same thing, automating an application by making calls that execute its functions, in this case to test the results are as expected. Since this is a WPF post lets assume that you are writing an application with MVVM, and the best way (to avoid brittleness when we are change the UI) is to ignore the UI (View) and call the layer that sits underneath i.e. the VM (ViewModel).
In fact its quite easy just to add a self-hosted WCF connection inside your BApp application so that it can be called externally.
this._host = new ServiceHost(service);
this._host.AddServiceEndpoint(typeof(IContract), new NetTcpBinding(), address);
this._host.Open();
This would then enable you to get the two talking totally independently.
If your Bapp is able to somehow invoke Win32 API - then this can be achieved by sending a custom WM_USER message to your Fapp - using SendMessage(). In your Fapp you handle this message and take appropriate action.
I don't think getting handle to a control and invoking its handler is the right way.
I am having a very difficult time trying to debug/fix an application.
Briefly:
- I created a "wizard" type app that starts with the user taking a photograph (using the common dialog for photos)
If the user tries to use the text input window (SIP) (the little keyboard input window) after a photo is taken the event loop seems to hang - the event is not processed or is delayed for a while.
If the user does not take a picture the SIP keyboard works great.
This only happens on some of my devices. Specifically it is not a problem on an MC65 but is a problem on an ES400.
It appears that the app's event loop gets screwed up with the way I am displaying forms and taking photos.
If created a simple test app with single form containing a button (Event handler takes a photo) and a text box that accepts input. That works fine. But it is only a single form app that does nothing else.
When I combine the photo taking with my form displaying (making a "wizard" ) things go badly.
I wonder what kind of event loop should I be running?
Essentially the user takes a photo then goes through some forms (I hide one form and show another when they click the "next" button.)
The Form.Show is called from the main form after a picture is taken and then I have something like:
while(UserNotFinished)
{
Application.DoEvents()
}
Where UserNotFinished is a flag set from my wizard/forms after the "submit" button is pressed.
I will be happy to provide more code but not sure what would be useful.
I am new to C# and CF development (lots of years of C++/Win32)
The real confusing part is that this works on one device but not on another. In fact, the device hangs completely. It ends the activesync connection and sometimes I have to hard reset by removing the battery.
I think your problem stems from the while(true) { DoEvents(); } and perhaps how you are trying to go between forms. The only time I've used the DoEvents() method is when I'm already in the scope of a windows event and I need to be sure something in the message queue is processed so screen updates are correct. I'd suggest making a controller class to manage the screen flow for your wizard. You can control the screen flow by either using ShowDialog() and execute the flow control directly in the scope of a single call, or you'll have to use Show() and an asynchronous mechanism such as subscribing to and handling specific form and control events in the controller class. Also saw the comment about introducing another thread, beware that Forms belong to the thread they were created in and you must Invoke(...) all Form members in the context of the creating thread.
Hmm. Very strange
I started a new thread and basically call Application.DoEvents() in in as well and it seems to fix the problem...
I don't know why the
while(true)
{
DoEvents()
}
in the main thread doesn't work.
First of all, we are in C#, WPF, desktop application (can be .NET 3.5).
I need to show login window when application starts, if login succeeds, hide login window and show main app window. In addition, when the main window is closed, one should see login window again. If login window is closed, application quits.
Bonus to make it harder: when "Remember user" option checked, login window is skipped and user is logged in automatically, showing the main window instatly. (please do not think about how the option is stored, assume you just know it and have the value in variable).
What is meant to be the application startpoint (means which window is considered to be in app.xaml StartupUri property)?
How would you solve architecture? Is the best way to use App_Startup event and show some window from that point?
What type of App.ShutdownMode would you use? And how would that work? You can use "OneLastWindowClose", "OnMainWindowClose" or "OnExplicitShutdown". What is your choice and why?
In general, I am interested in "what calls what, what is application 'root', what invokes closing the application".
I already have a solution for my problem that works, but I am interested, if any of you have met this problem and how have you solved it? I will gladly share my approach, but I don't want to limit your ideas in the first place.
Thank you for your contribution.
P.S.: I am using MVVM framework, that makes it just a little more messy, but the point stays. I also use Ninject as IoC, but this issue still of course stays on. I did not mention these information to make the question as clear as possible. I also have to handle exceptions (communication with server or db during the login may fail), and to make it really interesting, I must show interactive 'splashwindow' indicating what application does (loading, communication with server, autologin). But please stay away from these requirements at the moment, we can discuss them later.
I'm going to repeat your questions as I understand them, you can correct me where I am missing something. Also, I'm rather new to the whole M-V-VM thing, so take my advice with a grain of salt. The best way I know how to learn is to throw out my ideas and have them corrected.
You want to know things
1) Where should the logic exist to decide where to show the login or main window
2) Which shutdown mode to use
3) What is application root
1)
I believe the decision to show a login or auto login is business logic, thus should live in the Model of your M-V-VM framework. Once that logic is implemented, the view can display whichever window is required by querying the Model.
I assume your application has an App.xaml and App.cs file which runs when the application is executed. You can override OnStartup within App.cs and display whichever window is necessary, based on the results of the business logic (which are learned from using some object in your Model).
2)
For shutdown model, I'd probably go with OnLastWindowClose, but I have no idea how many windows your application is using. I assume only the two you mentioned (login and main).
3)
What is application root? I would argue that your Model is really the application root, in that it holds all of the important stuff (view is user interface to the model, view model is state for the view). So, when it comes to what is the root or essence of your application, I would argue it is some set of objects in your model.
What calls what? That will all depend on what you're trying to accomplish. In general, I avoid having the view model know anything about the view. Both view and view model can be aware of the model. In certain cases, you'll want to make use of Ninject as a service manager in order to inject a view into the view model (without forcing the view model to depend on the view).
An entirely different approach would be to avoid a log in window altogether and display a login method in the main window. You'll still need some Model object to tell you when to show the login prompt, but you won't have to worry about juggling windows. Honestly, I'd probably go that route. WPF provides us with a lot of interesting and sexy ways to accomplish that task.
The Application object is the root of any WPF project. You are correct that you can handle the App_Startup event - depending on what you need to do at startup, of course.
Depending on whether you need a more complex navigation framework or the application will be forever limited to the scenario you described, you can go a multitude of ways from there.
What I'm doing in the applications I work on is I create a Navigator object that handles navigation both between different windows and inside windows, then I simply call Navigator.Navigate(new MyViewModel(), NavMode.ReplaceWindow); which closes the previous 'main' window (if any is open) and displays a new one, setting the contents to the instance of my ViewModel. The ViewModel is then displayed using the correct templates. I can also use NavMode.ReplaceUserControl which replaces the contents of the current 'main' window, or NavMode.OpenModalWindow which displays a modal dialog. My ShutdownMode is set to OnLastWindowClose, since I can always call App.Shutdown() if I want to close regardless of the open windows.
Of course, when you replace windows you need to make sure to instantiate (not show, just instantiate) the new window before closing the old one, to avoid application shutdown because you have no more windows.
You could do something similar, but it's just an idea and it's tailored specifically for my requirements.