1 | using System;
|
---|
2 | using System.Collections.Generic;
|
---|
3 | using System.Linq;
|
---|
4 | using System.Text;
|
---|
5 | using System.Windows;
|
---|
6 | using HeuristicLab.PluginInfrastructure;
|
---|
7 | using System.Windows.Controls;
|
---|
8 | using AvalonDock;
|
---|
9 | using System.Windows.Media.Imaging;
|
---|
10 | using System.Reflection;
|
---|
11 | using System.Windows.Media;
|
---|
12 | using HeuristicLab.BackgroundProcessing;
|
---|
13 | using System.Threading;
|
---|
14 | using System.Windows.Threading;
|
---|
15 | using System.ComponentModel;
|
---|
16 | using System.Windows.Input;
|
---|
17 | using System.Windows.Controls.Primitives;
|
---|
18 | using HeuristicLab.Core;
|
---|
19 | using System.Windows.Forms.Integration;
|
---|
20 | using HeuristicLab.Common;
|
---|
21 |
|
---|
22 | namespace HeuristicLab.MainForm.WPF {
|
---|
23 |
|
---|
24 | public abstract class WPFMainFormBase : Window, IMainForm, IControlManager {
|
---|
25 |
|
---|
26 | public static readonly Size ToolBarImageSize = new Size(20, 20);
|
---|
27 | public abstract Type UserInterfaceItemType { get; }
|
---|
28 |
|
---|
29 | public WPFMainFormBase() {
|
---|
30 | InitializeComponent();
|
---|
31 | InstallExceptionHandlers();
|
---|
32 | InitializeBackgroundWorkerMonitoring();
|
---|
33 | BuildRootMenu();
|
---|
34 | BuildViewsMenu();
|
---|
35 | BuildToolBar();
|
---|
36 | Messages.Items.Clear();
|
---|
37 | DockingManager.PropertyChanged += (sender, args) => {
|
---|
38 | if (args.PropertyName == "ActiveDocument")
|
---|
39 | OnActiveDocumentChanged();
|
---|
40 | };
|
---|
41 | MainFormManager.RegisterMainForm(this);
|
---|
42 | ControlManager.RegisterManager(this);
|
---|
43 | }
|
---|
44 |
|
---|
45 | public void ShowControl(object control) {
|
---|
46 | object content = null;
|
---|
47 | string name = "Control";
|
---|
48 | if (control is HeuristicLab.MainForm.IView) {
|
---|
49 | content = control;
|
---|
50 | name = ((HeuristicLab.MainForm.IView)control).Caption;
|
---|
51 | }
|
---|
52 | ShowObjectAsView(content, name);
|
---|
53 | }
|
---|
54 |
|
---|
55 | public IContentView ShowContent(IContent content) {
|
---|
56 | throw new NotImplementedException();
|
---|
57 | }
|
---|
58 |
|
---|
59 | public IContentView ShowContent(IContent content, Type viewType) {
|
---|
60 | throw new NotImplementedException();
|
---|
61 | }
|
---|
62 |
|
---|
63 | #region GUI
|
---|
64 |
|
---|
65 | private DockPanel Panel = new DockPanel();
|
---|
66 | private Menu Menu = new Menu();
|
---|
67 | private ToolBar ToolBar = new ToolBar();
|
---|
68 | private StatusBar StatusBar = new StatusBar();
|
---|
69 | private StatusBar BackgroundWorkers = new OperationBarPanel();
|
---|
70 | private StatusBar Messages = new StatusBar();
|
---|
71 | private DocumentPane DocumentPane = new DocumentPane();
|
---|
72 | private DockingManager DockingManager = new DockingManager() {
|
---|
73 | MinHeight = 50,
|
---|
74 | };
|
---|
75 |
|
---|
76 | protected void InitializeComponent() {
|
---|
77 | StatusBar.Items.Add("OKB Cockpit");
|
---|
78 | StatusBar.Items.Add(new Separator());
|
---|
79 | StatusBar.Items.Add("Operations:");
|
---|
80 | StatusBar.Items.Add(BackgroundWorkers);
|
---|
81 | StatusBar.Items.Add(new Separator());
|
---|
82 | StatusBar.Items.Add(Messages);
|
---|
83 | StatusBar.AddHandler(Button.ClickEvent, new RoutedEventHandler(OperationAborted));
|
---|
84 | Menu.SetValue(DockPanel.DockProperty, Dock.Top);
|
---|
85 | ToolBar.SetValue(DockPanel.DockProperty, Dock.Top);
|
---|
86 | StatusBar.SetValue(DockPanel.DockProperty, Dock.Bottom);
|
---|
87 | DockingManager.Content = DocumentPane;
|
---|
88 | Panel.Children.Add(Menu);
|
---|
89 | Panel.Children.Add(ToolBar);
|
---|
90 | Panel.Children.Add(StatusBar);
|
---|
91 | Panel.Children.Add(DockingManager);
|
---|
92 | Content = Panel;
|
---|
93 | Title = "WPF Main Window";
|
---|
94 | }
|
---|
95 |
|
---|
96 | #endregion
|
---|
97 |
|
---|
98 | #region fallback exception handling
|
---|
99 |
|
---|
100 | private void InstallExceptionHandlers() {
|
---|
101 | AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
|
---|
102 | Dispatcher.UnhandledException += Dispatcher_UnhandledException;
|
---|
103 | WorkerMonitor.Default.BackgroundWorkerException += WorkerMonitor_BackgroundWorkerException;
|
---|
104 | }
|
---|
105 |
|
---|
106 | private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) {
|
---|
107 | Exception x = e.ExceptionObject as Exception;
|
---|
108 | if (x != null)
|
---|
109 | ShowException("Unhandled application domain exception", x);
|
---|
110 | }
|
---|
111 |
|
---|
112 | private void Dispatcher_UnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e) {
|
---|
113 | ShowException("Unhandled dispatcher exception", e.Exception);
|
---|
114 | }
|
---|
115 |
|
---|
116 | private void WorkerMonitor_BackgroundWorkerException(object sender, ThreadExceptionEventArgs e) {
|
---|
117 | ShowException(String.Format("'{0}' raised async exception", e.Exception.Message), e.Exception.InnerException);
|
---|
118 | }
|
---|
119 |
|
---|
120 | protected void ShowException(string message, Exception e) {
|
---|
121 | Dispatcher.BeginInvoke(new Action(() => {
|
---|
122 | MessageBox.Show(this,
|
---|
123 | String.Format("{0}:\n{1}", message, e.ToString()),
|
---|
124 | Title, MessageBoxButton.OK, MessageBoxImage.Error);
|
---|
125 | }), DispatcherPriority.SystemIdle);
|
---|
126 | }
|
---|
127 |
|
---|
128 | #endregion
|
---|
129 |
|
---|
130 | #region view discovery
|
---|
131 |
|
---|
132 | private void BuildViewsMenu() {
|
---|
133 | MenuItem viewsMenu = new MenuItem() { Header = "Views" };
|
---|
134 | Menu.Items.Add(viewsMenu);
|
---|
135 | foreach (Type viewType in ApplicationManager.Manager
|
---|
136 | .GetTypes(UserInterfaceItemType)
|
---|
137 | .Where(t => t.GetInterface(typeof(IAutoView).FullName) != null)) {
|
---|
138 | viewsMenu.Items.Add(CreateViewsMenuEntry(viewType));
|
---|
139 | }
|
---|
140 | }
|
---|
141 |
|
---|
142 | private MenuItem CreateViewsMenuEntry(Type viewType) {
|
---|
143 | MenuItem item = new MenuItem() { Header = viewType.Name };
|
---|
144 | item.Click += (sender, args) => {
|
---|
145 | ShowView((IView)Activator.CreateInstance(viewType, true));
|
---|
146 | };
|
---|
147 | return item;
|
---|
148 | }
|
---|
149 |
|
---|
150 | #endregion
|
---|
151 |
|
---|
152 | #region menu discovery
|
---|
153 |
|
---|
154 | private void BuildRootMenu() {
|
---|
155 | foreach (Type menuItemType in
|
---|
156 | ApplicationManager.Manager
|
---|
157 | .GetTypes(UserInterfaceItemType)
|
---|
158 | .Where(t => t.GetInterface(typeof(IMenuItem).FullName) != null)) {
|
---|
159 | IMenuItem item = (IMenuItem)Activator.CreateInstance(menuItemType, true);
|
---|
160 | AddMenuItem(Menu.Items, item, item.Structure.GetEnumerator());
|
---|
161 | }
|
---|
162 | }
|
---|
163 |
|
---|
164 | private void AddMenuItem(ItemCollection items, IMenuItem newItem, IEnumerator<string> structure) {
|
---|
165 | if (structure.MoveNext()) {
|
---|
166 | string name = structure.Current;
|
---|
167 | MenuItem parent = items.Cast<MenuItem>().SingleOrDefault(i => i.Header as string == name);
|
---|
168 | if (parent == null) {
|
---|
169 | parent = new MenuItem() { Header = name, Tag = newItem };
|
---|
170 | InsertSortedMenuItem(items, parent);
|
---|
171 | }
|
---|
172 | AddMenuItem(parent.Items, newItem, structure);
|
---|
173 | } else {
|
---|
174 | MenuItem item = new MenuItem() { Header = newItem.Name, Tag = newItem };
|
---|
175 | item.Click += (s, a) => newItem.Execute();
|
---|
176 | if (newItem.Image != null)
|
---|
177 | item.Icon = newItem.Image;
|
---|
178 | WPFMenuItem wpfItem = newItem as WPFMenuItem;
|
---|
179 | if (wpfItem != null) {
|
---|
180 | wpfItem.MenuItem = item;
|
---|
181 | item.IsEnabled = wpfItem.IsEnabled;
|
---|
182 | wpfItem.IsEnabledChanged += (s, a) => item.IsEnabled = wpfItem.IsEnabled;
|
---|
183 | }
|
---|
184 | InsertSortedMenuItem(items, item);
|
---|
185 | }
|
---|
186 | }
|
---|
187 |
|
---|
188 | private static void InsertSortedMenuItem(ItemCollection items, MenuItem menuItem) {
|
---|
189 | IMenuItem newItem = menuItem.Tag as IMenuItem;
|
---|
190 | if (newItem == null) {
|
---|
191 | items.Add(menuItem);
|
---|
192 | } else {
|
---|
193 | int index = 0;
|
---|
194 | while (index < items.Count) {
|
---|
195 | MenuItem it = items[index] as MenuItem;
|
---|
196 | if (it == null) break;
|
---|
197 | IMenuItem item = it.Tag as IMenuItem;
|
---|
198 | if (item == null) break;
|
---|
199 | if (newItem.Position < item.Position) break;
|
---|
200 | index++;
|
---|
201 | }
|
---|
202 | items.Insert(index, menuItem);
|
---|
203 | }
|
---|
204 | }
|
---|
205 |
|
---|
206 | #endregion
|
---|
207 |
|
---|
208 | #region tool bar discovery
|
---|
209 |
|
---|
210 | private void BuildToolBar() {
|
---|
211 | foreach (Type itemType in
|
---|
212 | ApplicationManager.Manager
|
---|
213 | .GetTypes(UserInterfaceItemType)
|
---|
214 | .Where(t => t.GetInterface(typeof(IToolBarItem).FullName) != null)) {
|
---|
215 | IToolBarItem item = (IToolBarItem)Activator.CreateInstance(itemType, true);
|
---|
216 | StackPanel visualItem = new StackPanel() { Orientation = Orientation.Vertical };
|
---|
217 | Image image =
|
---|
218 | ImageConverter.Win32Image2WPFImage(item.Image) ??
|
---|
219 | new Image() {
|
---|
220 | Source = new BitmapImage(new Uri(
|
---|
221 | "pack://application:,,,/" +
|
---|
222 | Assembly.GetExecutingAssembly().GetName().Name +
|
---|
223 | ";component/Images/Lambda.png"))
|
---|
224 | };
|
---|
225 | image.Width = ToolBarImageSize.Width;
|
---|
226 | image.Height = ToolBarImageSize.Height;
|
---|
227 | image.Stretch = Stretch.UniformToFill;
|
---|
228 | visualItem.Children.Add(image);
|
---|
229 | visualItem.Children.Add(new TextBlock() { Text = item.Name });
|
---|
230 | Button b = new Button() { Content = visualItem };
|
---|
231 | b.Click += (s, a) => item.Execute();
|
---|
232 | WPFToolBarItem wpfItem = item as WPFToolBarItem;
|
---|
233 | if (wpfItem != null) {
|
---|
234 | b.IsEnabled = wpfItem.IsEnabled;
|
---|
235 | if (wpfItem.IsVisible)
|
---|
236 | b.Visibility = Visibility.Visible;
|
---|
237 | else
|
---|
238 | b.Visibility = Visibility.Collapsed;
|
---|
239 | wpfItem.IsEnabledChanged += (s, a) => b.IsEnabled = wpfItem.IsEnabled;
|
---|
240 | wpfItem.IsVisibleChanged += (s, a) => {
|
---|
241 | if (wpfItem.IsVisible)
|
---|
242 | b.Visibility = Visibility.Visible;
|
---|
243 | else
|
---|
244 | b.Visibility = Visibility.Collapsed;
|
---|
245 | };
|
---|
246 | }
|
---|
247 | ToolBar.Items.Add(b);
|
---|
248 | }
|
---|
249 | }
|
---|
250 |
|
---|
251 | #endregion
|
---|
252 |
|
---|
253 | #region Background Worker Monitoring
|
---|
254 |
|
---|
255 | private void InitializeBackgroundWorkerMonitoring() {
|
---|
256 | var BackgroundWorkerView = new DispatchedView<ObservableBackgroundWorker>(
|
---|
257 | WorkerMonitor.Default, Dispatcher, DispatcherPriority.SystemIdle);
|
---|
258 | BackgroundWorkers.ItemsSource = BackgroundWorkerView;
|
---|
259 | BackgroundWorkerView.CollectionChanged += (sender, args) => {
|
---|
260 | if (args.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add) {
|
---|
261 | if (Cursor != Cursors.AppStarting)
|
---|
262 | Cursor = Cursors.AppStarting;
|
---|
263 | } else {
|
---|
264 | bool active = BackgroundWorkerView.Count() > 0;
|
---|
265 | if (active && Cursor == Cursors.Arrow) {
|
---|
266 | Cursor = Cursors.AppStarting;
|
---|
267 | } else if (!active && Cursor == Cursors.AppStarting) {
|
---|
268 | Cursor = Cursors.Arrow;
|
---|
269 | }
|
---|
270 | }
|
---|
271 | };
|
---|
272 | }
|
---|
273 |
|
---|
274 | protected void OperationAborted(object sender, RoutedEventArgs args) {
|
---|
275 | Button button = args.OriginalSource as Button;
|
---|
276 | if (button == null) return;
|
---|
277 | ObservableBackgroundWorker worker = button.Tag as ObservableBackgroundWorker;
|
---|
278 | if (worker == null) return;
|
---|
279 | if (worker.WorkerSupportsCancellation &&
|
---|
280 | MessageBox.Show(this,
|
---|
281 | String.Format("Do you want to cancel the background execution of '{0}'?", worker.Name), Title,
|
---|
282 | MessageBoxButton.YesNo, MessageBoxImage.Question) ==
|
---|
283 | MessageBoxResult.Yes)
|
---|
284 | worker.CancelAsync();
|
---|
285 | args.Handled = true;
|
---|
286 | }
|
---|
287 |
|
---|
288 | protected override void OnClosing(CancelEventArgs e) {
|
---|
289 | base.OnClosing(e);
|
---|
290 | if (WorkerMonitor.Default.Count() > 0) {
|
---|
291 | var result = MessageBox.Show(
|
---|
292 | this,
|
---|
293 | "You have tasks running in the background. Do you want to signal and wait for their termination?",
|
---|
294 | Title,
|
---|
295 | MessageBoxButton.YesNoCancel,
|
---|
296 | MessageBoxImage.Warning);
|
---|
297 | if (result == MessageBoxResult.Cancel || result == MessageBoxResult.None)
|
---|
298 | e.Cancel = true;
|
---|
299 | else if (result == MessageBoxResult.Yes)
|
---|
300 | WorkerMonitor.Default.CancelAllAsync();
|
---|
301 | Thread.Sleep(0);
|
---|
302 | }
|
---|
303 | }
|
---|
304 |
|
---|
305 | protected override void OnClosed(EventArgs e) {
|
---|
306 | AppDomain.CurrentDomain.UnhandledException -= CurrentDomain_UnhandledException;
|
---|
307 | Dispatcher.UnhandledException -= Dispatcher_UnhandledException;
|
---|
308 | WorkerMonitor.Default.BackgroundWorkerException += WorkerMonitor_BackgroundWorkerException;
|
---|
309 | base.OnClosed(e);
|
---|
310 | }
|
---|
311 |
|
---|
312 | #endregion
|
---|
313 |
|
---|
314 | #region Status Messages
|
---|
315 |
|
---|
316 | class Message : IStatusMessage {
|
---|
317 |
|
---|
318 | public string Text { get; set; }
|
---|
319 | public Action HideAction { get; set; }
|
---|
320 |
|
---|
321 | public override string ToString() {
|
---|
322 | return Text;
|
---|
323 | }
|
---|
324 |
|
---|
325 | public void Hide() {
|
---|
326 | HideAction();
|
---|
327 | }
|
---|
328 | }
|
---|
329 |
|
---|
330 | public interface IStatusMessage {
|
---|
331 | string Text { get; }
|
---|
332 | void Hide();
|
---|
333 | }
|
---|
334 |
|
---|
335 | public IStatusMessage ShowMessage(string message, TimeSpan duration) {
|
---|
336 | Message m = new Message() { Text = message };
|
---|
337 | DispatcherTimer timer = new DispatcherTimer() { Interval = duration };
|
---|
338 | Action hideAction = () => {
|
---|
339 | Messages.Items.Remove(m);
|
---|
340 | timer.Stop();
|
---|
341 | };
|
---|
342 | m.HideAction = hideAction;
|
---|
343 | Messages.Items.Add(m);
|
---|
344 | if (duration != TimeSpan.Zero) {
|
---|
345 | timer.Tick += (sender, args) => {
|
---|
346 | hideAction();
|
---|
347 | };
|
---|
348 | timer.Start();
|
---|
349 | }
|
---|
350 | return m;
|
---|
351 | }
|
---|
352 |
|
---|
353 | #endregion
|
---|
354 |
|
---|
355 | #region View management
|
---|
356 |
|
---|
357 | public event EventHandler<ViewEventArgs> ViewClosed;
|
---|
358 | public event EventHandler<ViewShownEventArgs> ViewShown;
|
---|
359 | public event EventHandler<ViewEventArgs> ViewHidden;
|
---|
360 |
|
---|
361 | protected void OnViewClosed(IView view) {
|
---|
362 | EventHandler<ViewEventArgs> handler = ViewClosed;
|
---|
363 | if (handler != null)
|
---|
364 | handler(this, new ViewEventArgs(view));
|
---|
365 | }
|
---|
366 |
|
---|
367 | protected void OnViewShown(IView view, bool firstTimeShown) {
|
---|
368 | EventHandler<ViewShownEventArgs> handler = ViewShown;
|
---|
369 | if (handler != null)
|
---|
370 | handler(this, new ViewShownEventArgs(view, firstTimeShown));
|
---|
371 | }
|
---|
372 |
|
---|
373 | protected void OnViewHidden(IView view) {
|
---|
374 | EventHandler<ViewEventArgs> handler = ViewHidden;
|
---|
375 | if (handler != null)
|
---|
376 | handler(this, new ViewEventArgs(view));
|
---|
377 | }
|
---|
378 |
|
---|
379 | public IEnumerable<IView> Views {
|
---|
380 | get {
|
---|
381 | return DockingManager.DockableContents
|
---|
382 | .Select(c => c.Content)
|
---|
383 | .Where(c => c is IView)
|
---|
384 | .Cast<IView>().AsEnumerable();
|
---|
385 | }
|
---|
386 | }
|
---|
387 |
|
---|
388 | public IView ActiveView {
|
---|
389 | get {
|
---|
390 | return DockingManager.ActiveContent as IView;
|
---|
391 | }
|
---|
392 | }
|
---|
393 |
|
---|
394 | protected void OnActiveDocumentChanged() {
|
---|
395 | EventHandler handler = ActiveViewChanged;
|
---|
396 | if (handler != null)
|
---|
397 | handler(null, EventArgs.Empty);
|
---|
398 | }
|
---|
399 |
|
---|
400 | public event EventHandler ActiveViewChanged;
|
---|
401 |
|
---|
402 | private void ShowObjectAsView(object o, string name) {
|
---|
403 | if (o as System.Windows.Forms.Control != null) {
|
---|
404 | o = new WindowsFormsHost() { Child = (System.Windows.Forms.Control)o };
|
---|
405 | }
|
---|
406 | var content = new DockableContent() {
|
---|
407 | Content = o,
|
---|
408 | Title = name,
|
---|
409 | HideOnClose = false,
|
---|
410 | };
|
---|
411 | DocumentPane.Items.Add(content);
|
---|
412 | DocumentPane.SelectedItem = content;
|
---|
413 | }
|
---|
414 |
|
---|
415 | public DockableContent AddView(IView view) {
|
---|
416 | try {
|
---|
417 | DockableContent content = new DockableContent() {
|
---|
418 | Content = view,
|
---|
419 | Title = view.Caption,
|
---|
420 | HideOnClose = true, // to enable capturing of state change events
|
---|
421 | };
|
---|
422 | content.StateChanged += (s, a) => {
|
---|
423 | if (a == DockableContentState.Hidden) {
|
---|
424 | OnViewClosed(view);
|
---|
425 | content.HideOnClose = false;
|
---|
426 | // HideOnClose moves content to _hiddenContents private member.
|
---|
427 | // To remove it from there, we have to show the content again
|
---|
428 | // to move it back into an accessible container and remove
|
---|
429 | // it from there.
|
---|
430 | Dispatcher.BeginInvoke(new Action(() => {
|
---|
431 | DockingManager.Show(content);
|
---|
432 | content.ContainerPane.Items.Remove(content);
|
---|
433 | }));
|
---|
434 | } else {
|
---|
435 | OnViewShown(view, false);
|
---|
436 | }
|
---|
437 | };
|
---|
438 | DocumentPane.Items.Add(content);
|
---|
439 | DocumentPane.SelectedItem = content;
|
---|
440 | OnViewShown(view, true);
|
---|
441 | return content;
|
---|
442 | } catch (Exception x) {
|
---|
443 | MessageBox.Show(this,
|
---|
444 | String.Format("Could not add view '{0}':\n{1}", view.Caption, x.ToString()),
|
---|
445 | Title,
|
---|
446 | MessageBoxButton.OK, MessageBoxImage.Exclamation);
|
---|
447 | return null;
|
---|
448 | }
|
---|
449 | }
|
---|
450 |
|
---|
451 | /// <summary>
|
---|
452 | /// Show existing view or create new one. Return true if this is the first time shown.
|
---|
453 | /// </summary>
|
---|
454 | public bool ShowView(IView view) {
|
---|
455 | DockableContent content = GetContent(view);
|
---|
456 | if (content == null) {
|
---|
457 | content = AddView(view);
|
---|
458 | return true;
|
---|
459 | } else {
|
---|
460 | DockingManager.Show(content);
|
---|
461 | content.ContainerPane.SelectedItem = content;
|
---|
462 | OnViewShown(view, false);
|
---|
463 | return false;
|
---|
464 | }
|
---|
465 | }
|
---|
466 |
|
---|
467 | public void HideView(IView view) {
|
---|
468 | DockableContent content = GetContent(view);
|
---|
469 | if (content != null)
|
---|
470 | DockingManager.Hide(content);
|
---|
471 | OnViewHidden(view);
|
---|
472 | }
|
---|
473 |
|
---|
474 | public void CloseView(IView view) {
|
---|
475 | DockableContent content = GetContent(view);
|
---|
476 | content.ContainerPane.Items.Remove(content);
|
---|
477 | OnViewClosed(view);
|
---|
478 | }
|
---|
479 |
|
---|
480 | public void CloseAllViews() {
|
---|
481 | foreach (var viewContainer in DockingManager.DockableContents.Where(c => c.Content is IView)) {
|
---|
482 | viewContainer.ContainerPane.Items.Remove(viewContainer);
|
---|
483 | OnViewClosed(viewContainer.Content as IView);
|
---|
484 | }
|
---|
485 | }
|
---|
486 |
|
---|
487 | private DockableContent GetContent(IView view) {
|
---|
488 | DockableContent content =
|
---|
489 | DockingManager
|
---|
490 | .DockableContents
|
---|
491 | .SingleOrDefault(c => c.Content as IView == view);
|
---|
492 | return content;
|
---|
493 | }
|
---|
494 |
|
---|
495 | #endregion
|
---|
496 |
|
---|
497 | #region IMainForm
|
---|
498 |
|
---|
499 | public event EventHandler Changed;
|
---|
500 |
|
---|
501 | protected void OnChanged() {
|
---|
502 | EventHandler handler = Changed;
|
---|
503 | if (handler != null)
|
---|
504 | handler(this, EventArgs.Empty);
|
---|
505 | }
|
---|
506 |
|
---|
507 | #endregion
|
---|
508 | }
|
---|
509 | }
|
---|