I have a an Winform application with 2 forms.
In one form I have a Tab Control with 3 Tabs and navigation buttons to switch between tabs.
On the first tab the user selects a file and on navigating to next tab i want to do some
processing on the file selected in the first tab,and show the result in the 3rd tab.
The other form just invokes this form(start app.)
How can i do this using MVC/MVP ?
Currently i have created nested forms.
Starting application form creates instance of tab form and on SelectedIndexChanged on
the tab control and matching the selected tab property I'm doing the processing in the starting application form.and On closing on the tab form setting the result in the
starting application form.(which isn't ideal).
Edit : Also each tab contains a User Control which i have to initialize on tab change (refereeing to the data selected in the previous tab.)
Simple example is selecting a .zip file in the first tab , clicking next
will show the list of files within the zip file and in the third tab do processing with
the file selected in the 2nd tab.(there are better ways to do the same..just for sake of example.)
EDIT 2 : Basically I'm confused on how to get values from first tab via controller, do processing, and pass it to the next tab( via controller) and set the user control properties on the 2nd tab (via controller).Also the Tab titles are removed ..please see ..so the Tab form looks more like a wizard form. that's why i was using the SelectedIndexChanged property.
Basically i need to separate view and processing logic from the Winform.
Thanks All.
Odd choices for a UI. Anyhoo, there is no reason whatsoever to wait for SelectedIndexChanged to process the file. You might as well do it as soon as the file is selected. It will work better, the tab control becomes more responsive. If you wait until the event then the control will be frozen for a while as your UI thread is busy iterating the .zip file. The user will not consider this desirable.
Makes the MVC implementation a lot simpler too, whatever it might look like. Extra bonus is that you now no longer depend on the TabControl and can use whatever controls are best for the job.
Your Model will deal with your zip file in this case, e.g. methods like Print(), Extract() etc. and events like PrintCompleted and ExtractCompleted etc.
Your IView will expose methods and events that abstract your interaction with the UI behind an interface. So perhaps some methods such as DisplayFolderContents() and events like FileSelected() etc.
Your presenter will hook up to the Model and IView events and control which methods are called on each etc. The Form that you have a TabControl on is just an implemenation of the IView interface. You could have a different view just by injecting an implementation of IView into the Presenter, including a Mock IView for testing.
Values can be passed around the MVP pattern through the EventArgs you use.
/// <summary>
/// The event args for a selected file.
/// </summary>
public class FileSelectedEventArgs : EventArgs
{
public string FileName { get; private set; }
public FileSelectedEventArgs(string fileName)
{
this.FileName = fileName;
}
}
When the user selects a file, the FileSelected event is raised in your View, with the FileName available in the FileSelectedEventArgs. The Presenter listens for this event, and calls a method on the Model - maybe ExtractFile(string fileName), passing in the fileName from the FileSelectedEventArgs from the View.
The Presenter then listens to the ExtractCompleted event to be fired from the Model (which also has whatever custom eventargs you want) and calls the appropriate method on your View, passing in the parameters from the Model. The method in the View can do whatever you want in terms of displaying the data in a TabControl or in another way.
This is just one way of doing it anyway.
Related
I have a selection of TextBoxes that a user fills in when they wish to note that they have had contact with another person. Most of the TextBoxes are imply filled in by typing into them. However, for one of them I would like the user to be able to select from a list of People that appears when they click on a button.
This is where I am having problems. So far I have just made a DataGrid appear and handled it's SelectionChanged method to fill in the TextBoxes text property. This has worked fine, however now there is not enough space on the current page to show an entire DataGrid with all the people they can select from.
I've decided to show the People in a separate, smaller Window that appears when the user clicks a Button. The issue I have is that when the user selects the Person they wish to mark the contact for in the new Window, I have no idea how I can notify the original Window that a Person has been selected, close the new smaller Window and fill in the appropriate TextBox on the original Window.
What would be the most intuitive way to fill in the TextBox on the original Window, based on the selection on the Window that opens?
I would use delegates,which call a function of the original window and parse the changed variable with it. So you know when the user clicked something and you can directly react to this "event".
Link:
https://msdn.microsoft.com/en-us/library/ms173171.aspx
If you use a framework like Galasoft's MVVM Light (http://www.galasoft.ch/), they have a messenger system just for this purpose. It allows you to "broadcast" messages that can be "received" by any other part of the application
This is when considering using Domain, Model, Presentation (Winforms/WPF version of MVC formatting) to do your app.
You can have each form as its own class, well they are their own class. Create each form class but add some public members to it if the controls are private. Have them have "get" properties only and to return the values of whatever controls or variables are in that form. Your main form will be the controlling form. All forms will be handled by the main form so when you open it, it is a class the main form can access.
Now, if I remember (been doing more MVC and not any Winforms lately) I believe if you use the ShowDialog() method it will freeze the main thread so when you close out the main form you can continue and read in public members you have in your forms class you opened. Synchronous I believe it runs as. If you use just Show() the thread will keep on trucking, asynchronous. With asynchronous you may then have to use a main form in your startup code so there is always a window there but subscribe to the close event of your forms and have a method that can grab those public members out. Be sure to instantiate the extra forms at the root of the main class so it doesn't fall out of scope when it exists the method that calls it. You may even be able to make the method that calls is a async call and have an await before the command that runs the Show method on the form.
Summary, treat each form as its own class but add public members that can read the values from the controls and/or variables you want. Read that data from the class when it closes via an event or synchronously when the thread closes out from the form closing. The form closing doesn't discard the object, just the visualization of the form.
Oh, if you are passing info from the main form to a child for you are opening, either add a constructor for that form class that takes your input as a model or values to fill in the appropriate variables or forms before showing it or create a public property you can put your values you want to send in before showing the class.
Remember, everything is a class, once you look at it as such and treat it as such, the answer will come. :-)
I should warn, I am a long winded explainer.
At work putting all this down from memory so some errors may exist. Let me know if there are.
I think the problem is to access the controls of the main window, isn`t it?
You can define an event of changing user`s choise and access MainWindow control by using the following construction:
((MainWindow)Application.Current.MainWindow).MyTextBox
Motivation:
I'd like to have a 'File->Save As' MenuItem that behaves just like in Visual Studio. When there is nothing opened it says "Save Selected Items as..."
and when a particular file (e.g. SomeFile.cs) is opened in a tab, the MenuItem reads "Save SomeFile.cs as...".
My App architecture (MVVM, using MVVM Light):
MainWindow.xaml:
<MenuItem Header="{Binding SelectedProjectName}" HeaderStringFormat="Save {0} As..." />
MainWindowViewModel:
I hold a collection of opened tabs (opened files)
private ObservableCollection<BaseProjectViewModel> _projects;
I have a property returning a currently selected tab
public BaseProjectViewModel SelectedProject
{
get
{
return _selectedProject;
}
set
{
if (_selectedProject == value)
{
return;
}
_selectedProject = value;
RaisePropertyChanged("SelectedProject");
RaisePropertyChanged("SelectedProjectName");
}
}
I created a property returning the name of the file in the currently selected tab
public string SelectedProjectName
{
get
{
if (SelectedProject == null)
{
return "Selected Item";
}
return SelectedProject.SafeFileName;
}
}
BaseProjectViewModel serves as a base class for various file types. Each file type has its own class derived from BaseProjectViewModel. It has properties like for example
PaneHeader that returns a string to be displayed in pane header,
SafeFileName that returns just the file name of a path etc...
Question:
When I change the name of the file (thus changing properties of the BaseProjectViewModel) how do I trigger RaisePropertyChanged of the SelectedProjectName in MainWindowViewModel?
What is the cleanest way to do that?
My thoughts
I thought of two possible ways to do that, but i don't know if any of them is the correct way to do it:
(In short) Listening to CollectionChanged on _projects. When there is add/remove -> subscribe/ubsubscribe an event handler that would
look if the PropertyName is the one we are looking for and if yes subsequently call RaisePropertyChanged("SelectedProjectName")?
Use something like MVVM Light Messaging.
Question 2: If you don't suggest any other way and in fact you'd suggest one of these two - could you please elaborate on advantages and disadvantages?
EDIT
I created a very simple project to demonstrate the issue - LINK.
When you run the project:
'New' adds a new TabItem. When text is edited, the TabHeader is decorated with an asterisk.
'Save {0}' menu item "saves" the selected TabItem (simulated by removing the asterisk). I didn't want to complicate the example and introduce a SaveFileDialog and such.
'Save As {0}' menu item simulates Save as in such a way that it adds 'X' character to the end od Tab header string.
When no TabItem is selected, the {0} resolves to "Selected Item".
When you have one tab selected, you click SaveAs() and open the menu, you'll notice that change has not been raised on SelectedProjectName property. When you click another tab and then select the first one back, the change is propagated.
Edit for Erno: What I fail to understand is this: Let's suppose I have a special menu for each document type. Let's suppose I have one particular tab selected (with it's own menu enabled/visible, the other collapsed). How is it going to help me propagate the PropertyChanged of PaneHeader property in BaseProjectViewMode to SelectedProjectName in MainWindowViewModel? If you have time could you please demonstrate it on the example? I also would like to ask you what would be an alternate way if I wanted/neede to do the wiring? Thank you in advance.
From your options I dislike #1 because it might introduce a lot of wiring that is hard to track and maintain.
Option #2 might be OK but could end up in the same wiring mess as #1 but because of the messaging it will be less visible.
I'd consider a different approach:
put a menu in the MainWindow that is responsible for handling commands when no files are open or selected.
when a document is opened in a view and has the focus: replace the current menu with the document specific menu. (like MDI applications work in WinForms)
This way you can customize the Menu per document (type) and it does not require the event dependencies.
i built an Winform application with Pcapdot.Net DLL's that take Pcap file and play all the file packets into the machine network card.
recently i have notice that all my main form (where all the buttons clicks\events) is a mess, i putt all the method inside those function and the code looks complicated and it's hard to understand so i started to rearrange this code.
because my application has Listbox where all the played files is inside i put all this play function inside the Play button for example: i have loop over my Listbox.Items.Count and inside i am handle this files. now i want to do something different and this is my question:
is it common way to define class that handle this Play function and from the main form every time that file added to my Listbox just fire up the event ControlAdded or after remove file ControlRemoved and put this files into my class who has List that hold this file ?
I think you would be better off rearranging it more like this (to separate the business logic from your UI):
Have a public method inside your Form class that returns an IEnumerable<string> which is the list of files, called something like SelectedFiles().
Make a public event property for the "Play" button which is raised when the user clicks the "Play" button, called something like PlayClicked.
Make a "controller" class which is responsible for creating and showing the form.
Your controller class would attach to the PlayClicked event. The controller's handler for PlayClicked would call the form's SelectedFiles() method to get the list of files, and would then do whatever it needs to do.
I would even consider wrapping the handling code for the selected files into another class called something like SelectedFileHandler and put the logic for handling the files into that, perhaps in a method called HandleFiles(IEnumerable<string> files).
Then the "controller" class would still be hooked up to the PlayClicked event, but it would use the SelectedFileHandler.HandleFiles() method to handle the files.
Does this make some sense to you? I could be misunderstanding what you're looking for.
I'm basically saying that you should use a Model View Controller or a Model View Presenter architecture.
I just started breaking up my GUI application into UserControls. I have a TabControl with a bunch of TagePages. Obviously my MainForm.cs file was filled up with tons of events and controls etc and it got very messy quick.
So a previous question gained me the insight of how to create a UserControl. I intend on creating a UserControl for each TabPage and I was wondering how I can interact with Components on the main form or other UserControls.
Here is an example of a TabPage that I have made using a UserControl, which needs to Enable or Disable a button depending which TabPage is currently selected. Is this proper usage or is there a better way?
public partial class TabDetails : UserControl
{
private RequestForm fRequestForm;
public TabDetails()
{
InitializeComponent();
}
public void CustomInitialization(RequestForm pRequestForm)
{
fRequestForm = pRequestForm;
pRequestForm.TabControl_Main.SelectedIndexChanged += SelectedTabIndexChanged;
}
private void SelectedTabIndexChanged(object pSender, EventArgs pEvents)
{
fRequestForm.Button_SubmitRequest.Enabled = fRequestForm.TabControl_Main.SelectedTab != fRequestForm.Tab_Details;
}
}
In the MainForm.cs constructor I call:
this.tab_Details1.CustomInitialization(this);
This doesn't look like a good use of a user control. The user control should not decide how things in the form should behave when something is changed in the user control. A user control should be unaware of its container and should operate in any container.
The user control should notify the form that something has changed without telling what's the internal implementation and the form should decide what to do.
Example:
A user control named "NameUserControl" consists of TitleComboBox, FirstNameTextBox and LastNameTextBox. The user control wants to notify when one of the values has changed.
Wrong Way:
Create events:
TitleComboBox - SelectedIndexChanged.
FirstNameTextBox, LastNameTextBox - TextChanged.
The problems here:
You expose the internal controls behavior. What will happen if you want to change the TitleComboBox to TextBox? You'll have to change the event name and implementation.
You expose the fact that you use exactly 3 different controls. What will happen if you want to use the same text box for first and last name? You'll have to delete one event and change the name of the other.
Good Way:
Create only a single event: NameChanged and expose 1 property of FullName or three different properties for the values.
Either way the form subscribe to the event and decide what to do next.
Another thing to think about: the more you add more functionality to your user control, you either make it less reusable or you make its code more complex. For example, if you add validation inside the user control, you'll find one day that you need it without validation, so you'll add a property "bool ValidateData" or it will be so complicated that you'll need to build another control. One way to solve that is to build very small user controls, but combine them in one or more bigger user controls that fit all your current needs.
I have a parent control (main form) and a child control (user control). The child control has some code, which determines what functions the application can perform (e.g. save files, write logs etc.). I need to show/hide, enable/disable main menu items of the main form according to the functionality. As I can't just write MainMenu.MenuItem1.Visible = false; (the main menu is not visible from the child control), I fire an event in the child control and handle this event on the main form. The problem is I need to pass what elements of the menu need to be shown/hidden. To do this I created an enum, showing what to do with the item
public enum ItemMode
{
TRUE, FALSE, NONE
}
Then I created my eventargs which have 6 parameters of type ItemMode (there are 6 menu items I need to manage). So any time I need to show the 1st item, hide the 2nd and do nothing with the rest I have to write something like this
e = new ItemModeEventArgs(ItemMode.TRUE, ItemMode.FALSE, ItemMode.NONE, ItemMode.NONE, ItemMode.NONE, ItemMode.NONE);
FireMyEvent(e);
This seems like too much code to me and what's more, what if I need to manage 10 items in future? Then I will have to rewrite all the constructors just to add 4 more NONEs.
I believe there's a better way of doing this, but I just can't figure out what it is.
you could create an EventArgs which takes an ItemMode[] or a List<ItemMode> or a Dictionary<string, ItemMode> for those items (instead of the current 6 arguments) - that way you don't need to change much when adding more items...
The chain child->parent can be reversed. In such scenario requests will be passed from the mainform to its child controls.
Controls participating in the command processing must implement a special interface:
interface ICommandHandler
{
bool CanInvoke(int commandId);
void InvokeCommand(int commandId);
bool UpdateCommand(int commandId, MenuItem item);
}
The advantage of this approach is that only active controls must be traversed, not all the children.
The weak point - UpdateCommand() method, which could be called from Application.Idle event or timer.
hope this helps
Well, I can't speak to a "best" way unless except in specific cases, since there are often several equally good ways. My first thought, though, would be to create a class that has a property which the parent assigns a reference of its MainMenu, and which has functions for enabling/disabling individual menus or items. In a very simple case, this could be as simple as passing a list of strings like "OptionsScreen=enabled" etc. and then inside the class manually handling those cases, to something more generic like passing strings such as "mnuToolsOptions=enabled" and then finding the menu item via the .Name property. So, on startup, create an instance of your menu handler class, then do something like MenuHandlerHelper.MenuToHandle = MainMenuStrip;.
On the child side, you could perhaps have your classes that update the MainMenu be derived UserObjects that derive from a common one you create that has a public MyMainMenuHandlerHelper MenuHandlerHelper property, and set that in your Parent form's constructor so the Child controls could call the menu updating function. Or, you could have an event that just passed back a List<string> containing all the rules, and fire that as you are doing now.
This is a very simple idea, and doesn't handle things like possible collisions, so you would probably either want to throw an exception (easiest). You might also want to have rule priorities (easy), or try to chain functionality (could be hard to determine orders and such).
I would be happy to implement some examples of my thinking if you can constrain the problem a little for me (desired collision handling, etc.) and I actually wanted to see what some basic code would look like and try perhaps to test a couple of ideas, so if those come to anything I will post the code here for those as well.
If you want to handle all changes from the user control: you could inherit your own user control class and add a reference to the form/collection of menu entries you want to be able to modify. You would pass this reference to its constructor and then you'll be able to easily modify the menu from inside your user control
If, on the other hand, you would like to manage this on an event basis in your form, you could implement your own EventArgs class, but I would do it like this:
class ItemModeEventArgs
{
MenuItemClass target;
EnumType change;
}
So basically for each menu item a separate event is risen. Every event args knows about what item menu is changing and how it is changing. Ofc, if you only have two states for the menu items, the 'change' field is kinda useless.
This way you don't have to hardcode functions with n parameters where n is the number of menu items.
There truly are many ways this could be done. The easiest way, although some will shout "bad practice", would be to just pass a pointer to the main menu when the control is created. Your control would have some code like this:
MenuStrip MainMenu;
internal void SetMainMenu(MenuStrip mainMenu)
{
MainMenu = mainMenu;
}
and when you create the control:
void CreateControl()
{
MyUserControlType MyControl = new MyUserControlType();
MyControl.SetMainMenu(mainMenuStrip); //or whatever you called your main menu
}
This will give your child form unlimited access to the mainform's menu (which is why it's technically a bad practice). From the child form you can access the submenus by name, eg:
if (MainMenu != null)
{
ToolStripMenuItem fileMenu =
(ToolStripMenuItem)MainMenu.Items["fileToolStripMenuItem"];
fileMenu.DropDownItems["exportFileToolStripItem"].Visible = false;
}
If you created the control in the designer, then you can add the SetMainMenu call into the .design file, or add it in the Form's load event.