using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Windows; using HeuristicLab.PluginInfrastructure; using System.Windows.Controls; using AvalonDock; using System.Windows.Media.Imaging; using System.Reflection; using System.Windows.Media; using HeuristicLab.BackgroundProcessing; using System.Threading; using System.Windows.Threading; using System.ComponentModel; using System.Windows.Input; using System.Windows.Controls.Primitives; using HeuristicLab.Core; using System.Windows.Forms.Integration; using HeuristicLab.Common; namespace HeuristicLab.MainForm.WPF { public abstract class WPFMainFormBase : Window, IMainForm, IControlManager { public static readonly Size ToolBarImageSize = new Size(20, 20); public abstract Type UserInterfaceItemType { get; } public WPFMainFormBase() { InitializeComponent(); InstallExceptionHandlers(); InitializeBackgroundWorkerMonitoring(); BuildRootMenu(); BuildViewsMenu(); BuildToolBar(); Messages.Items.Clear(); DockingManager.PropertyChanged += (sender, args) => { if (args.PropertyName == "ActiveDocument") OnActiveDocumentChanged(); }; MainFormManager.RegisterMainForm(this); ControlManager.RegisterManager(this); } public void ShowControl(object control) { object content = null; string name = "Control"; if (control is HeuristicLab.MainForm.IView) { content = control; name = ((HeuristicLab.MainForm.IView)control).Caption; } ShowObjectAsView(content, name); } public IContentView ShowContent(IContent content) { throw new NotImplementedException(); } public IContentView ShowContent(IContent content, Type viewType) { throw new NotImplementedException(); } #region GUI private DockPanel Panel = new DockPanel(); private Menu Menu = new Menu(); private ToolBar ToolBar = new ToolBar(); private StatusBar StatusBar = new StatusBar(); private StatusBar BackgroundWorkers = new OperationBarPanel(); private StatusBar Messages = new StatusBar(); private DocumentPane DocumentPane = new DocumentPane(); private DockingManager DockingManager = new DockingManager() { MinHeight = 50, }; protected void InitializeComponent() { StatusBar.Items.Add("OKB Cockpit"); StatusBar.Items.Add(new Separator()); StatusBar.Items.Add("Operations:"); StatusBar.Items.Add(BackgroundWorkers); StatusBar.Items.Add(new Separator()); StatusBar.Items.Add(Messages); StatusBar.AddHandler(Button.ClickEvent, new RoutedEventHandler(OperationAborted)); Menu.SetValue(DockPanel.DockProperty, Dock.Top); ToolBar.SetValue(DockPanel.DockProperty, Dock.Top); StatusBar.SetValue(DockPanel.DockProperty, Dock.Bottom); DockingManager.Content = DocumentPane; Panel.Children.Add(Menu); Panel.Children.Add(ToolBar); Panel.Children.Add(StatusBar); Panel.Children.Add(DockingManager); Content = Panel; Title = "WPF Main Window"; } #endregion #region fallback exception handling private void InstallExceptionHandlers() { AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; Dispatcher.UnhandledException += Dispatcher_UnhandledException; WorkerMonitor.Default.BackgroundWorkerException += WorkerMonitor_BackgroundWorkerException; } private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) { Exception x = e.ExceptionObject as Exception; if (x != null) ShowException("Unhandled application domain exception", x); } private void Dispatcher_UnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e) { ShowException("Unhandled dispatcher exception", e.Exception); } private void WorkerMonitor_BackgroundWorkerException(object sender, ThreadExceptionEventArgs e) { ShowException(String.Format("'{0}' raised async exception", e.Exception.Message), e.Exception.InnerException); } protected void ShowException(string message, Exception e) { Dispatcher.BeginInvoke(new Action(() => { MessageBox.Show(this, String.Format("{0}:\n{1}", message, e.ToString()), Title, MessageBoxButton.OK, MessageBoxImage.Error); }), DispatcherPriority.SystemIdle); } #endregion #region view discovery private void BuildViewsMenu() { MenuItem viewsMenu = new MenuItem() { Header = "Views" }; Menu.Items.Add(viewsMenu); foreach (Type viewType in ApplicationManager.Manager .GetTypes(UserInterfaceItemType) .Where(t => t.GetInterface(typeof(IAutoView).FullName) != null)) { viewsMenu.Items.Add(CreateViewsMenuEntry(viewType)); } } private MenuItem CreateViewsMenuEntry(Type viewType) { MenuItem item = new MenuItem() { Header = viewType.Name }; item.Click += (sender, args) => { ShowView((IView)Activator.CreateInstance(viewType, true)); }; return item; } #endregion #region menu discovery private void BuildRootMenu() { foreach (Type menuItemType in ApplicationManager.Manager .GetTypes(UserInterfaceItemType) .Where(t => t.GetInterface(typeof(IMenuItem).FullName) != null)) { IMenuItem item = (IMenuItem)Activator.CreateInstance(menuItemType, true); AddMenuItem(Menu.Items, item, item.Structure.GetEnumerator()); } } private void AddMenuItem(ItemCollection items, IMenuItem newItem, IEnumerator structure) { if (structure.MoveNext()) { string name = structure.Current; MenuItem parent = items.Cast().SingleOrDefault(i => i.Header as string == name); if (parent == null) { parent = new MenuItem() { Header = name, Tag = newItem }; InsertSortedMenuItem(items, parent); } AddMenuItem(parent.Items, newItem, structure); } else { MenuItem item = new MenuItem() { Header = newItem.Name, Tag = newItem }; item.Click += (s, a) => newItem.Execute(); if (newItem.Image != null) item.Icon = newItem.Image; WPFMenuItem wpfItem = newItem as WPFMenuItem; if (wpfItem != null) { wpfItem.MenuItem = item; item.IsEnabled = wpfItem.IsEnabled; wpfItem.IsEnabledChanged += (s, a) => item.IsEnabled = wpfItem.IsEnabled; } InsertSortedMenuItem(items, item); } } private static void InsertSortedMenuItem(ItemCollection items, MenuItem menuItem) { IMenuItem newItem = menuItem.Tag as IMenuItem; if (newItem == null) { items.Add(menuItem); } else { int index = 0; while (index < items.Count) { MenuItem it = items[index] as MenuItem; if (it == null) break; IMenuItem item = it.Tag as IMenuItem; if (item == null) break; if (newItem.Position < item.Position) break; index++; } items.Insert(index, menuItem); } } #endregion #region tool bar discovery private void BuildToolBar() { foreach (Type itemType in ApplicationManager.Manager .GetTypes(UserInterfaceItemType) .Where(t => t.GetInterface(typeof(IToolBarItem).FullName) != null)) { IToolBarItem item = (IToolBarItem)Activator.CreateInstance(itemType, true); StackPanel visualItem = new StackPanel() { Orientation = Orientation.Vertical }; Image image = ImageConverter.Win32Image2WPFImage(item.Image) ?? new Image() { Source = new BitmapImage(new Uri( "pack://application:,,,/" + Assembly.GetExecutingAssembly().GetName().Name + ";component/Images/Lambda.png")) }; image.Width = ToolBarImageSize.Width; image.Height = ToolBarImageSize.Height; image.Stretch = Stretch.UniformToFill; visualItem.Children.Add(image); visualItem.Children.Add(new TextBlock() { Text = item.Name }); Button b = new Button() { Content = visualItem }; b.Click += (s, a) => item.Execute(); WPFToolBarItem wpfItem = item as WPFToolBarItem; if (wpfItem != null) { b.IsEnabled = wpfItem.IsEnabled; if (wpfItem.IsVisible) b.Visibility = Visibility.Visible; else b.Visibility = Visibility.Collapsed; wpfItem.IsEnabledChanged += (s, a) => b.IsEnabled = wpfItem.IsEnabled; wpfItem.IsVisibleChanged += (s, a) => { if (wpfItem.IsVisible) b.Visibility = Visibility.Visible; else b.Visibility = Visibility.Collapsed; }; } ToolBar.Items.Add(b); } } #endregion #region Background Worker Monitoring private void InitializeBackgroundWorkerMonitoring() { var BackgroundWorkerView = new DispatchedView( WorkerMonitor.Default, Dispatcher, DispatcherPriority.SystemIdle); BackgroundWorkers.ItemsSource = BackgroundWorkerView; BackgroundWorkerView.CollectionChanged += (sender, args) => { if (args.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add) { if (Cursor != Cursors.AppStarting) Cursor = Cursors.AppStarting; } else { bool active = BackgroundWorkerView.Count() > 0; if (active && Cursor == Cursors.Arrow) { Cursor = Cursors.AppStarting; } else if (!active && Cursor == Cursors.AppStarting) { Cursor = Cursors.Arrow; } } }; } protected void OperationAborted(object sender, RoutedEventArgs args) { Button button = args.OriginalSource as Button; if (button == null) return; ObservableBackgroundWorker worker = button.Tag as ObservableBackgroundWorker; if (worker == null) return; if (worker.WorkerSupportsCancellation && MessageBox.Show(this, String.Format("Do you want to cancel the background execution of '{0}'?", worker.Name), Title, MessageBoxButton.YesNo, MessageBoxImage.Question) == MessageBoxResult.Yes) worker.CancelAsync(); args.Handled = true; } protected override void OnClosing(CancelEventArgs e) { base.OnClosing(e); if (WorkerMonitor.Default.Count() > 0) { var result = MessageBox.Show( this, "You have tasks running in the background. Do you want to signal and wait for their termination?", Title, MessageBoxButton.YesNoCancel, MessageBoxImage.Warning); if (result == MessageBoxResult.Cancel || result == MessageBoxResult.None) e.Cancel = true; else if (result == MessageBoxResult.Yes) WorkerMonitor.Default.CancelAllAsync(); Thread.Sleep(0); } } protected override void OnClosed(EventArgs e) { AppDomain.CurrentDomain.UnhandledException -= CurrentDomain_UnhandledException; Dispatcher.UnhandledException -= Dispatcher_UnhandledException; WorkerMonitor.Default.BackgroundWorkerException += WorkerMonitor_BackgroundWorkerException; base.OnClosed(e); } #endregion #region Status Messages class Message : IStatusMessage { public string Text { get; set; } public Action HideAction { get; set; } public override string ToString() { return Text; } public void Hide() { HideAction(); } } public interface IStatusMessage { string Text { get; } void Hide(); } public IStatusMessage ShowMessage(string message, TimeSpan duration) { Message m = new Message() { Text = message }; DispatcherTimer timer = new DispatcherTimer() { Interval = duration }; Action hideAction = () => { Messages.Items.Remove(m); timer.Stop(); }; m.HideAction = hideAction; Messages.Items.Add(m); if (duration != TimeSpan.Zero) { timer.Tick += (sender, args) => { hideAction(); }; timer.Start(); } return m; } #endregion #region View management public event EventHandler ViewClosed; public event EventHandler ViewShown; public event EventHandler ViewHidden; protected void OnViewClosed(IView view) { EventHandler handler = ViewClosed; if (handler != null) handler(this, new ViewEventArgs(view)); } protected void OnViewShown(IView view, bool firstTimeShown) { EventHandler handler = ViewShown; if (handler != null) handler(this, new ViewShownEventArgs(view, firstTimeShown)); } protected void OnViewHidden(IView view) { EventHandler handler = ViewHidden; if (handler != null) handler(this, new ViewEventArgs(view)); } public IEnumerable Views { get { return DockingManager.DockableContents .Select(c => c.Content) .Where(c => c is IView) .Cast().AsEnumerable(); } } public IView ActiveView { get { return DockingManager.ActiveContent as IView; } } protected void OnActiveDocumentChanged() { EventHandler handler = ActiveViewChanged; if (handler != null) handler(null, EventArgs.Empty); } public event EventHandler ActiveViewChanged; private void ShowObjectAsView(object o, string name) { if (o as System.Windows.Forms.Control != null) { o = new WindowsFormsHost() { Child = (System.Windows.Forms.Control)o }; } var content = new DockableContent() { Content = o, Title = name, HideOnClose = false, }; DocumentPane.Items.Add(content); DocumentPane.SelectedItem = content; } public DockableContent AddView(IView view) { try { DockableContent content = new DockableContent() { Content = view, Title = view.Caption, HideOnClose = true, // to enable capturing of state change events }; content.StateChanged += (s, a) => { if (a == DockableContentState.Hidden) { OnViewClosed(view); content.HideOnClose = false; // HideOnClose moves content to _hiddenContents private member. // To remove it from there, we have to show the content again // to move it back into an accessible container and remove // it from there. Dispatcher.BeginInvoke(new Action(() => { DockingManager.Show(content); content.ContainerPane.Items.Remove(content); })); } else { OnViewShown(view, false); } }; DocumentPane.Items.Add(content); DocumentPane.SelectedItem = content; OnViewShown(view, true); return content; } catch (Exception x) { MessageBox.Show(this, String.Format("Could not add view '{0}':\n{1}", view.Caption, x.ToString()), Title, MessageBoxButton.OK, MessageBoxImage.Exclamation); return null; } } /// /// Show existing view or create new one. Return true if this is the first time shown. /// public bool ShowView(IView view) { DockableContent content = GetContent(view); if (content == null) { content = AddView(view); return true; } else { DockingManager.Show(content); content.ContainerPane.SelectedItem = content; OnViewShown(view, false); return false; } } public void HideView(IView view) { DockableContent content = GetContent(view); if (content != null) DockingManager.Hide(content); OnViewHidden(view); } public void CloseView(IView view) { DockableContent content = GetContent(view); content.ContainerPane.Items.Remove(content); OnViewClosed(view); } public void CloseAllViews() { foreach (var viewContainer in DockingManager.DockableContents.Where(c => c.Content is IView)) { viewContainer.ContainerPane.Items.Remove(viewContainer); OnViewClosed(viewContainer.Content as IView); } } private DockableContent GetContent(IView view) { DockableContent content = DockingManager .DockableContents .SingleOrDefault(c => c.Content as IView == view); return content; } #endregion #region IMainForm public event EventHandler Changed; protected void OnChanged() { EventHandler handler = Changed; if (handler != null) handler(this, EventArgs.Empty); } #endregion } }