/// <summary> /// Opens an object using the main thread containing a message handler. /// </summary> /// <param name="viewerObject">The object to be opened.</param> private void OpenObjectInForeground(object viewerObject) { try { // Activate the application when opening an object. This is used in situations where a notification comes in while // another application is active. When the user clicks on the notification widow, they will be routed to this // application, which then needs to be active to take the user's input. this.Activate(); // Close down any documents that are currently open in the viewer. if (this.activeViewer != null) { this.activeViewer.Close(); } // If an object type was selected that doesn't have a viewer (or the viewer couldn't be loaded), then // reject the operation and leave the last viewer up. Viewer viewer = this.viewerTable[viewerObject.GetType()]; if (viewer == null) { throw new Exception("Viewer not found"); } // This will synchronize the navigators with the open document. this.folderList.Open(viewerObject); this.guardianBar.Open(viewerObject); this.Text = viewerObject.ToString() + " - " + Properties.Settings.Default.ApplicationName; // This is an optimization: if the viewer for the current object is the same as the viewer for the last // object, then we'll skip the step of swapping the screen elements around and just reuse the current // viewer. Otherwise, there's some modest reorgainization of the screen required to activate the proper // viewer. if (this.activeViewer != viewer) { // The code below will modify the layout of the FormMain frame window. It will swap out the current // viewer its menus and toolbars and replace it with the new viewer and it's resources. Suspending // the layout will minimize the screen distractions. SuspendLayout(); // Swap the new active viewer with the previous one. Also, have the viewer cleared out so it's empty // the next time it's activated. This gets rid of the disturbing effect of seeing the previous data // when you select a new report. It shows up momentarily while the new document is constructed. // Clearing it out now will give it a chance to create a blank report in the background. viewer.BringToFront(); this.menuStrip.SuspendLayout(); this.toolStrip.SuspendLayout(); // Clear the previous menu and tools from the child viewer out of the main container area. ToolStripManager.RevertMerge(this.menuStrip); ToolStripManager.RevertMerge(this.toolStrip); // Merge the container's menu with the menu of the active viewer. ToolStripManager.Merge(viewer.MenuStrip, this.menuStrip); ToolStripManager.Merge(viewer.ToolBarStandard, this.toolStrip); this.menuStrip.ResumeLayout(); this.toolStrip.ResumeLayout(); // Let the screen process the changes. this.ResumeLayout(); this.activeViewer = viewer; } // Opening is a somewhat involved operation. Because it may take a few seconds to open a report, the // message loop can't be suspended while the user waits. The operation is done asynchronously. This // operation will kick off an asynchronous operation in the viewer that will gather all the resources // needed and draw the document. When that operation is complete, the viewer will kick off an event that // will signal the 'Open' operation is complete. At that point, a message will be broadcast from the // control and picked up by the 'viewer_EndOpenDocument' method below which will complete the operation of // opening the viewer and document. this.activeViewer.Open(viewerObject); } catch (Exception exception) { // These user interface errors need to be show to the users. MessageBox.Show(exception.Message, "Guardian Error", MessageBoxButtons.OK, MessageBoxIcon.Exclamation); } }
/// <summary>Initializes the Dynamically Loaded Viewers</summary> /// <remarks> /// The application is constructed as a container for navigators, document viewers and special purpose dialog boxes. Said /// differently, we want the application to be extensible and have incremental upgrades, so there's no hard coded way of /// saying "If I click on this icon, I want this user control displayed." The binding between the database objects and the /// Viewers (special purpose user interface controls) is done dynamically through a configuration file. The configuration /// file has a list of DLLs and the objects in those DLLs that can be used for viewing. Each of the viewers or special /// purpose dialog boxes has an integer associated with it. This integer is the 'type code' which corresponds to the type /// codes found in the 'types' table on the database. When we get a request to open up one of these objects, we use the /// type code to map the 'Open' request to one of the dynamically loaded modules. This hash table is used to associate the /// type code with the proper viewer. /// </remarks> public void InitializeViewers() { // The viewing area of the frame can have several viewers which all sit on top of each other in the same space. The // active viewer is the one that is visible and sits on top of all the rest. this.activeViewer = null; // This table contains a mapping between all the object types and the viewers used to present the data in those // types. When an object is opened, this table is searched for that object's type and the viewer that is part of the // key pair is loaded into the viewer area and the object is opened up into that viewer. this.viewerTable = new Dictionary <Type, Viewer>(); // Though a viewer can be specified to display several different types, only one instance of that viewer will be // created. This table helps organize the viewers during initialization so there is only one instance of a viewer for // the entire application. this.viewerTypeTable = new Dictionary <Type, Viewer>(); // Load any dynamic viewers into the application. ViewerSection viewerSection = (ViewerSection)ConfigurationManager.GetSection("viewers"); if (viewerSection != null) { // This loop will load each add-in module and associate it with an object type code. foreach (ViewerInfo viewerInfo in viewerSection) { // The most likely error to catch is the dynamically loaded control doesn't exist. In any case, a failure to // load any of the add-ins is not fatal. try { // This holds the next viewer to be initialized from the application settings. Viewer viewer = null; // If the specified viewer has already been created for another type, it will be re-used. The idea here is // that the viewer will have the intelligence to work out how to display any given data type. if (!viewerTypeTable.TryGetValue(viewerInfo.ViewerType, out viewer)) { viewer = (Viewer)viewerInfo.ViewerType.Assembly.CreateInstance(viewerInfo.ViewerType.ToString()); this.viewerTypeTable.Add(viewerInfo.ViewerType, viewer); } // This is where the mapping between the object type and the viewer used to display that object is kept. // When a navigator selects an object, it will send it to a Viewer. The Viewer will pull apart the data in // the object and determine how to show it to the user. But the first step is to pass the object on to the // appropriate Viewer. this.viewerTable.Add(viewerInfo.Type, viewer); // The Viewer will fill the panel in which it is placed. viewer.Dock = DockStyle.Fill; // This will give the viewers a method to ask the container to open a new object. viewer.ObjectOpen += new OpenObjectEventHandler(this.OpenObject); // This is the panel where all the viewers will be displayed. this.splitContainer2.Panel2.Controls.Add(viewer); } catch (Exception exception) { // Catch the most general errors and emit them to the debug devide. string errorMessage = String.Format("Viewer {0} couldn't be loaded: {1}", viewerInfo.ViewerType, exception.Message); MessageBox.Show(errorMessage, "Guardian Error", MessageBoxButtons.OK, MessageBoxIcon.Error); } } } }