// Load the window position and size from the registry private static void AvalonLayout_LoadWindowPositionAndSizeFromRegistry(this TimelapseWindow timelapse, string registryKey) { // Retrieve the window position and size Rect windowRect = timelapse.State.GetTimelapseWindowPositionAndSizeFromRegistryRect(registryKey); // Height and Width should not be negative. There was an instance where it was, so this tries to catch it just in case windowRect.Height = Math.Abs(windowRect.Height); windowRect.Width = Math.Abs(windowRect.Width); // Adjust the window position and size, if needed, to fit into the current screen dimensions windowRect = timelapse.FitIntoASingleScreen(windowRect); timelapse.Left = windowRect.Left; timelapse.Top = windowRect.Top; timelapse.Width = windowRect.Width; timelapse.Height = windowRect.Height; foreach (var floatingWindow in timelapse.DockingManager.FloatingWindows) { windowRect = new Rect(floatingWindow.Left, floatingWindow.Top, floatingWindow.Width, floatingWindow.Height); windowRect = timelapse.FitIntoASingleScreen(windowRect); floatingWindow.Left = windowRect.Left; floatingWindow.Top = windowRect.Top; floatingWindow.Width = windowRect.Width; floatingWindow.Height = windowRect.Height; } }
// As Deserialization rebuilds the Docking Manager, we need to reset the original layoutAnchorable and layoutDocument pointers to the rebuilt ones // Note if we define new LayoutAnchorables and LayoutDocuments in the future, we will have to modify this method accordingly private static void AvalonDock_ResetAfterDeserialize(this TimelapseWindow timelapse) { IEnumerable <LayoutAnchorable> layoutAnchorables = timelapse.DockingManager.Layout.Descendents().OfType <LayoutAnchorable>(); foreach (LayoutAnchorable layoutAnchorable in layoutAnchorables) { if (layoutAnchorable.ContentId == timelapse.DataEntryControlPanel.ContentId) { timelapse.DataEntryControlPanel = layoutAnchorable; } } IEnumerable <LayoutDocument> layoutDocuments = timelapse.DockingManager.Layout.Descendents().OfType <LayoutDocument>(); foreach (LayoutDocument layoutDocument in layoutDocuments) { if (layoutDocument.ContentId == timelapse.InstructionPane.ContentId) { timelapse.InstructionPane = layoutDocument; } else if (layoutDocument.ContentId == timelapse.ImageSetPane.ContentId) { timelapse.ImageSetPane = layoutDocument; } else if (layoutDocument.ContentId == timelapse.DataGridPane.ContentId) { timelapse.DataGridPane = layoutDocument; } } }
// Try to load a layout from the registry given the registry key private static bool AvalonLayout_TryLoadFromRegistry(this TimelapseWindow timelapse, string registryKey) { // Check the arguments for null ThrowIf.IsNullArgument(timelapse, nameof(timelapse)); // Retrieve the layout configuration from the registry string layoutAsString = timelapse.State.GetFromRegistry(registryKey); if (string.IsNullOrEmpty(layoutAsString)) { return(false); } // Convert the string to a stream MemoryStream layoutAsStream = new MemoryStream(); using (StreamWriter writer = new StreamWriter(layoutAsStream)) { writer.Write(layoutAsString); writer.Flush(); layoutAsStream.Position = 0; // Deserializa and load the layout XmlLayoutSerializer serializer = new XmlLayoutSerializer(timelapse.DockingManager); using (StreamReader streamReader = new StreamReader(layoutAsStream)) { serializer.Deserialize(streamReader); } return(true); } }
/// <summary> /// Save the current Avalon layout to the registry under the given registry key /// and the current timelapse window position and size under the given registry key with the added suffix /// </summary> /// <param name="timelapse"></param> /// <param name="registryKey"></param> /// <returns>false if there are any issues</returns> public static bool AvalonLayout_TrySave(this TimelapseWindow timelapse, string registryKey) { // Check the arguments for null ThrowIf.IsNullArgument(timelapse, nameof(timelapse)); // Serialization normally creates a stream, so we have to do a few contortions to transform that stream into a string StringBuilder xmlText = new StringBuilder(); using (XmlWriter xmlWriter = XmlWriter.Create(xmlText)) { // Serialize the layout into a string XmlLayoutSerializer serializer = new XmlLayoutSerializer(timelapse.DockingManager); using (StringWriter stream = new StringWriter()) { serializer.Serialize(xmlWriter); } if (!String.IsNullOrEmpty(xmlText.ToString().Trim())) { // Write the string to the registry under the given key name timelapse.State.WriteToRegistry(registryKey, xmlText.ToString()); AvalonLayout_TrySaveWindowPositionAndSizeAndMaximizeState(timelapse, registryKey); return(true); } else { return(false); } } }
/// <summary> /// Save the various window positions, size and mazimize state to the registry /// </summary> /// <param name="timelapse"></param> /// <param name="registryKey"></param> public static void AvalonLayout_TrySaveWindowPositionAndSizeAndMaximizeState(this TimelapseWindow timelapse, string registryKey) { // Check the arguments for null ThrowIf.IsNullArgument(timelapse, nameof(timelapse)); timelapse.AvalonLayout_SaveWindowPositionAndSizeToRegistry(registryKey + Constant.AvalonDockValues.WindowRegistryKeySuffix); timelapse.AvalonLayout_SaveWindowMaximizeStateToRegistry(registryKey + Constant.AvalonDockValues.WindowMaximizeStateRegistryKeySuffix); }
// Try to load a layout from the given resourceFilePath private static bool AvalonLayout_TryLoadFromResource(this TimelapseWindow timelapse, string resourceFilePath) { // Check the arguments for null ThrowIf.IsNullArgument(timelapse, nameof(timelapse)); XmlLayoutSerializer serializer = new XmlLayoutSerializer(timelapse.DockingManager); Uri uri = new Uri(resourceFilePath); try { using (Stream stream = System.Windows.Application.GetResourceStream(uri).Stream) { serializer.Deserialize(stream); } } catch { // Should only happen if there is something wrong with the uri address, e.g., if the resource doesn't exist return(false); } return(true); }
public DarkImagesThreshold(TimelapseWindow owner, FileDatabase fileDatabase, TimelapseUserRegistrySettings state, int currentImageIndex) : base(owner) { // Check the arguments for null ThrowIf.IsNullArgument(fileDatabase, nameof(fileDatabase)); ThrowIf.IsNullArgument(state, nameof(state)); this.InitializeComponent(); this.Owner = owner; this.fileDatabase = fileDatabase; this.imageEnumerator = new FileTableEnumerator(fileDatabase); this.imageEnumerator.TryMoveToFile(currentImageIndex); this.darkPixelThreshold = state.DarkPixelThreshold; this.darkPixelRatio = state.DarkPixelRatioThreshold; this.darkPixelRatioFound = 0; this.disposed = false; this.isColor = false; this.updateImageQualityForAllSelectedImagesStarted = false; this.state = state; dispatcherTimer.Tick += this.DispatcherTimer_Tick; dispatcherTimer.Interval = new TimeSpan(0, 0, 0, 0, 100); }
// Save the current timelapse window maximize state to the registry private static void AvalonLayout_SaveWindowMaximizeStateToRegistry(this TimelapseWindow timelapse, string registryKey) { bool windowStateIsMaximized = timelapse.WindowState == WindowState.Maximized; timelapse.State.WriteToRegistry(registryKey, windowStateIsMaximized); }
// Save the current timelapse window position and size to the registry private static void AvalonLayout_SaveWindowPositionAndSizeToRegistry(this TimelapseWindow timelapse, string registryKey) { Rect windowPositionAndSize = new Rect(timelapse.Left, timelapse.Top, timelapse.Width, timelapse.Height); timelapse.State.WriteToRegistry(registryKey, windowPositionAndSize); }
// Retrieve the maximize state from the registry and set the timelapse window to that state private static void AvalonLayout_LoadWindowMaximizeStateFromRegistry(this TimelapseWindow timelapse, string registryKey) { bool windowMaximizeState = timelapse.State.GetTimelapseWindowMaximizeStateFromRegistryBool(registryKey); timelapse.WindowState = windowMaximizeState ? WindowState.Maximized : WindowState.Normal; }
/// <summary> /// Try to load the layout identified by the layoutKey, which, depending on the key, is stored in the resource file or the registry /// This includes various adjustments, as detailed in the comments below. /// </summary> /// <param name="timelapse"></param> /// <param name="layoutKey"></param> /// <returns></returns> public static bool AvalonLayout_TryLoad(this TimelapseWindow timelapse, string layoutKey) { // Check the arguments for null ThrowIf.IsNullArgument(timelapse, nameof(timelapse)); bool isResourceFile = false; string layoutName = String.Empty; // Layouts are loaded from either the registry or from a resource file // If from the registry, then the registry lookup key is the the layoutKey // If from the resource file, then we have to use the path of the resource file switch (layoutKey) { case Constant.AvalonLayoutTags.DataEntryOnTop: layoutName = Constant.AvalonLayoutResourcePaths.DataEntryOnTop; isResourceFile = true; break; case Constant.AvalonLayoutTags.DataEntryOnSide: layoutName = Constant.AvalonLayoutResourcePaths.DataEntryOnSide; isResourceFile = true; break; case Constant.AvalonLayoutTags.DataEntryFloating: layoutName = Constant.AvalonLayoutResourcePaths.DataEntryFloating; isResourceFile = true; break; default: layoutName = layoutKey; break; } bool result; try { if (isResourceFile) { // Load thelayout from the resource file result = timelapse.AvalonLayout_TryLoadFromResource(layoutName); } else { // Load both the layout and the window position/size from the registry result = timelapse.AvalonLayout_TryLoadFromRegistry(layoutName); if (result) { timelapse.AvalonLayout_LoadWindowPositionAndSizeFromRegistry(layoutName + Constant.AvalonDockValues.WindowRegistryKeySuffix); timelapse.AvalonLayout_LoadWindowMaximizeStateFromRegistry(layoutName + Constant.AvalonDockValues.WindowMaximizeStateRegistryKeySuffix); } } } catch { // If for some reason loading the avalon layout fails, catch that and then reload from scratch. // Note that if this is the result of a corrupt registry entry, // that will self-correct as that entry will be over-written with new values after we load and image set and exit. result = false; } if (result == false) { // We are trying to load the last-used layout, but there isn't one. As a fallback, // we use the default configuration as specified in the XAML: - all tiled with the data entry on top. // Eve so, we check to see if the window position and size were saved; if they aren't there, it defaults to a reasonable size and position. timelapse.AvalonLayout_LoadWindowPositionAndSizeFromRegistry(layoutName + Constant.AvalonDockValues.WindowRegistryKeySuffix); timelapse.AvalonLayout_LoadWindowMaximizeStateFromRegistry(layoutName + Constant.AvalonDockValues.WindowMaximizeStateRegistryKeySuffix); return(result); } // After deserializing, a completely new LayoutRoot object is created. // This means we have to reset various things so the documents in the new object behave correctly. // This includes resetting the callbacks to the DataGrid.IsActiveChanged timelapse.DataEntryControlPanel.PropertyChanging -= timelapse.LayoutAnchorable_PropertyChanging; timelapse.DataGridPane.IsActiveChanged -= timelapse.DataGridPane_IsActiveChanged; timelapse.AvalonDock_ResetAfterDeserialize(); timelapse.DataGridPane.IsActiveChanged += timelapse.DataGridPane_IsActiveChanged; timelapse.DataEntryControlPanel.PropertyChanging += timelapse.LayoutAnchorable_PropertyChanging; // Force an update to the DataGridPane if its visible, as the above doesn't trigger it if (timelapse.DataGridPane.IsVisible) { timelapse.DataGridPane_IsActiveChanged(true); } // Force an update to the DataEntryControlPanel if its visible, as the above doesn't trigger it timelapse.DataEntryControlPanel.IsVisible = true; // Special case for DataEntryFloating: // Reposition the floating window in the middle of the main window, but just below the top // Note that this assumes there is just a single floating window (which should be the case for this configuration) if (layoutKey == Constant.AvalonLayoutTags.DataEntryFloating) { if (timelapse.DockingManager.FloatingWindows.Any()) { // BUG. This doesn't work, although it used to in older versions of AvalonDock. // It seems we need to call Float() to get these positions to 'stick', as otherwise the positions are done // relative to the primary screen. But if we call Float, it crashes as PreviousParent is null (in Float) foreach (var floatingWindow in timelapse.DockingManager.FloatingWindows) { // We set the DataEntry Control Panel top / left as it remembers the values (i.e. so the layout will be saved correctly later) // If we set the floating window top/left directly, it won't remember those values as its just the view. timelapse.DataEntryControlPanel.FloatingTop = timelapse.Top + 100; timelapse.DataEntryControlPanel.FloatingLeft = timelapse.Left + ((timelapse.Width - floatingWindow.Width) / 2.0); } // This used to cause the above values to 'stick', but it no longer works. //timelapse.DataEntryControlPanel.Float(); } } return(true); }
/// <summary> /// Fit the window so that its entirety is within a single screen /// </summary> /// <param name="timelapse"></param> /// <param name="windowRect"></param> /// <returns>A rectangle containing the modified window coordinates as wleft, wtop, wwidth, wheight </returns> public static Rect FitIntoASingleScreen(this TimelapseWindow timelapse, Rect windowRect) { try { // Find the screen (if any) that contains the window PresentationSource source = PresentationSource.FromVisual(timelapse); // The screen contiaing the window Screen screenContainingWindow = null; // WPF Coordinates of the screen that contains the window System.Windows.Point screenTopLeft = new System.Windows.Point(0, 0); System.Windows.Point screenBottomRight = new System.Windows.Point(0, 0); // The primary screen (which we will use in case we can't find a containing window) Screen primaryScreen = null; int typicalTaskBarHeight = 55; // Search the screens for the ones containing the top left of the window, if any foreach (Screen screen in Screen.AllScreens) { // Record if its the primary screen, if (screen.Primary) { primaryScreen = screen; } if (screenContainingWindow != null) { continue; } // Get the coordinates of the currentscreen and transform it into wpf coordinates. // Note that we subtract the task bar height as well. screenTopLeft.X = screen.Bounds.Left; screenTopLeft.Y = screen.Bounds.Top; screenBottomRight.X = screen.Bounds.Left + screen.Bounds.Width; screenBottomRight.Y = screen.Bounds.Top + screen.Bounds.Height - typicalTaskBarHeight; screenTopLeft = source.CompositionTarget.TransformFromDevice.Transform(screenTopLeft); screenBottomRight = source.CompositionTarget.TransformFromDevice.Transform(screenBottomRight); // If the upper left corner of the window is in this screen, then we have found the screen containing the window if (windowRect.Left >= screenTopLeft.X && windowRect.Left < screenBottomRight.X && windowRect.Top >= screenTopLeft.Y && windowRect.Top < screenBottomRight.Y) { screenContainingWindow = screen; } } // If none of the screens contains the window, then we will fit it into the primary screen at the window's width and height at the origin if (screenContainingWindow == null) { // Get the coordinates of the currentscreen and transform it into wpf coordinates. // Note that we subtract the task bar height as well. screenTopLeft.X = primaryScreen.Bounds.Left; screenTopLeft.Y = primaryScreen.Bounds.Top; screenBottomRight.X = primaryScreen.Bounds.Left + primaryScreen.Bounds.Width; screenBottomRight.Y = primaryScreen.Bounds.Top + primaryScreen.Bounds.Height - typicalTaskBarHeight; screenTopLeft = source.CompositionTarget.TransformFromDevice.Transform(screenTopLeft); screenBottomRight = source.CompositionTarget.TransformFromDevice.Transform(screenBottomRight); screenContainingWindow = primaryScreen; } // We allow some space for the task bar, assuming its visible at the screen's bottom // and place the window at the very top. Note that this won't cater for the situation when // the task bar is at the top of the screen, but so it goes. double screen_width = Math.Abs(screenBottomRight.X - screenTopLeft.X); double screen_height = Math.Abs(screenBottomRight.Y - screenTopLeft.Y); // Ensure that we have valid coordinates double wleft = Double.IsNaN(windowRect.Left) ? 0 : windowRect.Left; double wtop = Double.IsNaN(windowRect.Top) ? 0 : windowRect.Top; double wheight = Double.IsNaN(windowRect.Height) ? 740 : windowRect.Height; double wwidth = Double.IsNaN(windowRect.Height) ? 1024 : windowRect.Width; // If the window's height is larger than the screen's available height, // reposition it to the screen's top and and adjust its height to fill the available height if (wheight > screen_height) { wheight = screen_height; wtop = screenTopLeft.Y; } // If the window's width is larger than the screen's available width, // reposition it to the left and and adjust its width to fill the available width if (wwidth > screen_width) { wwidth = screen_width; wleft = screenTopLeft.X; } double wbottom = wtop + wheight; double wright = wleft + wwidth; // move window up if it extends below the working area if (wbottom > screenBottomRight.Y) { double pixelsToMoveUp = wbottom - screenBottomRight.Y; if (pixelsToMoveUp > screen_height) { // window is too tall and has to shorten to fit screen wtop = 0; wheight = screen_height; } else if (pixelsToMoveUp > 0) { // move window up wtop -= pixelsToMoveUp; } } // move window down if it extends above the working area if (wtop < screenTopLeft.Y) { double pixelsToMoveDown = Math.Abs(screenTopLeft.Y - wtop); if (pixelsToMoveDown > screen_height) { // window is too tall and has to shorten to fit screen wtop = 0; wheight = screen_height; } else if (pixelsToMoveDown > 0) { // move window up wtop += pixelsToMoveDown; } } // move window left if it extends right of the working area if (wright > screenBottomRight.X) { double pixelsToMoveLeft = wright - screenBottomRight.X; if (pixelsToMoveLeft > screen_width) { // window is too wide and has to narrow to fit screen wleft = screenTopLeft.X; wwidth = screen_width; } else if (pixelsToMoveLeft > 0) { // move window left wleft -= pixelsToMoveLeft; } } // move window right if it extends left of the working area if (wleft < screenTopLeft.X) { double pixelsToMoveRight = screenTopLeft.X - wleft; if (pixelsToMoveRight > 0) { // move window left wleft += pixelsToMoveRight; } if (wleft + wwidth > screen_width) { // window is too wide and has to narrow to fit screen wwidth = screenBottomRight.Y - wright; } } return(new Rect(wleft, wtop, wwidth, wheight)); } catch { // System.Diagnostics.Debug.Print("Catch: Problem in TimelapseAvalonExtensions - FitIntoScreen"); return(new Rect(5, 5, 740, 740)); } }