Zasadniczo, gdy użytkownik zmieni rozmiar okna mojej aplikacji, chcę, aby aplikacja miała taki sam rozmiar po ponownym otwarciu aplikacji.
Na początku myślałem o obsłużeniu zdarzenia SizeChanged i zapisaniu Height i Width, ale myślę, że musi być łatwiejsze rozwiązanie.
Dość prosty problem, ale nie mogę znaleźć łatwego rozwiązania.
Odpowiedzi:
Zapisz wartości w pliku user.config.
Musisz utworzyć wartość w pliku ustawień - powinna znajdować się w folderze Właściwości. Utwórz pięć wartości:
Top
typudouble
Left
typudouble
Height
typudouble
Width
typudouble
Maximized
typubool
- aby zatrzymać, czy okno jest zmaksymalizowane, czy nie. Jeśli chcesz przechowywać więcej informacji, potrzebny będzie inny typ lub struktura.Zainicjuj pierwsze dwa na 0, a drugie dwa na domyślny rozmiar aplikacji, a ostatnie na false.
Utwórz procedurę obsługi zdarzeń Window_OnSourceInitialized i dodaj następujące elementy:
this.Top = Properties.Settings.Default.Top; this.Left = Properties.Settings.Default.Left; this.Height = Properties.Settings.Default.Height; this.Width = Properties.Settings.Default.Width; // Very quick and dirty - but it does the job if (Properties.Settings.Default.Maximized) { WindowState = WindowState.Maximized; }
UWAGA: Ustawione rozmieszczenie okna musi zostać wprowadzone w zdarzeniu zainicjowanym na źródle okna, a nie konstruktorze, w przeciwnym razie, jeśli masz zmaksymalizowane okno na drugim monitorze, zawsze zostanie ponownie uruchomione zmaksymalizowane na monitorze podstawowym i nie będzie aby uzyskać do niego dostęp.
Utwórz procedurę obsługi zdarzeń Window_Closing i dodaj następujące elementy:
if (WindowState == WindowState.Maximized) { // Use the RestoreBounds as the current values will be 0, 0 and the size of the screen Properties.Settings.Default.Top = RestoreBounds.Top; Properties.Settings.Default.Left = RestoreBounds.Left; Properties.Settings.Default.Height = RestoreBounds.Height; Properties.Settings.Default.Width = RestoreBounds.Width; Properties.Settings.Default.Maximized = true; } else { Properties.Settings.Default.Top = this.Top; Properties.Settings.Default.Left = this.Left; Properties.Settings.Default.Height = this.Height; Properties.Settings.Default.Width = this.Width; Properties.Settings.Default.Maximized = false; } Properties.Settings.Default.Save();
To się nie powiedzie, jeśli użytkownik zmniejszy obszar wyświetlania - poprzez odłączenie ekranu lub zmianę rozdzielczości ekranu - podczas gdy aplikacja jest zamknięta, więc przed zastosowaniem wartości należy dodać sprawdzenie, czy żądana lokalizacja i rozmiar są nadal aktualne.
źródło
Właściwie nie musisz do tego używać kodu związanego (z wyjątkiem zapisywania ustawień). Możesz użyć niestandardowego rozszerzenia znaczników, aby powiązać rozmiar i położenie okna z ustawieniami takimi jak:
<Window x:Class="WpfApplication1.Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:my="clr-namespace:WpfApplication1" Title="Window1" Height="{my:SettingBinding Height}" Width="{my:SettingBinding Width}" Left="{my:SettingBinding Left}" Top="{my:SettingBinding Top}">
Kod tego rozszerzenia znaczników można znaleźć tutaj: http://www.thomaslevesque.com/2008/11/18/wpf-binding-to-application-settings-using-a-markup-extension/
źródło
{Binding Settings.Height}
, jak itp.Chociaż możesz „przewinąć własne” i ręcznie zapisać gdzieś ustawienia, i ogólnie to zadziała, bardzo łatwo jest nie obsłużyć poprawnie wszystkich przypadków. O wiele lepiej jest pozwolić systemowi operacyjnemu wykonać pracę za Ciebie, wywołując GetWindowPlacement () przy wyjściu i SetWindowPlacement () podczas uruchamiania. Obsługuje wszystkie szalone przypadki krawędzi, które mogą wystąpić (wiele monitorów, zapisz normalny rozmiar okna, jeśli jest zamknięte, gdy jest zmaksymalizowane, itp.), Więc nie musisz tego robić.
Ten przykład MSDN pokazuje, jak używać ich z aplikacją WPF. Próbka nie jest idealna (okno zostanie uruchomione w lewym górnym rogu tak małe, jak to możliwe przy pierwszym uruchomieniu, a projektant ustawień zapisuje wartość typu
WINDOWPLACEMENT
), ale powinien przynajmniej zacząć.źródło
Wiązanie "długiej formy", które Thomas opublikował powyżej, nie wymaga prawie żadnego kodowania, po prostu upewnij się, że masz powiązanie przestrzeni nazw:
<Window x:Class="WpfApplication1.Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:p="clr-namespace:WpfApplication1.Properties" Title="Window1" Height="{Binding Source={x:Static p:Settings.Default}, Path=Height, Mode=TwoWay}" Width="{Binding Source={x:Static p:Settings.Default}, Path=Width, Mode=TwoWay}" Left="{Binding Source={x:Static p:Settings.Default}, Path=Left, Mode=TwoWay}" Top="{Binding Source={x:Static p:Settings.Default}, Path=Top, Mode=TwoWay}">
Następnie, aby zaoszczędzić na kodzie związanym z:
private void frmMain_Closed(object sender, EventArgs e) { Properties.Settings.Default.Save(); }
źródło
WindowState="{Binding Source={x:Static properties:Settings.Default}, Path=WindowState, Mode=TwoWay}"
Alternatywnie możesz również polubić następujące podejście ( patrz źródło ). Dodaj klasę WindowSettings do swojego projektu i wstaw
WindowSettings.Save="True"
w nagłówku swojego głównego okna:<Window x:Class="YOURPROJECT.Views.ShellView" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:Services="clr-namespace:YOURNAMESPACE.Services" Services:WindowSettings.Save="True">
Gdzie WindowSettings jest zdefiniowany w następujący sposób:
using System; using System.ComponentModel; using System.Configuration; using System.Windows; namespace YOURNAMESPACE.Services { /// <summary> /// Persists a Window's Size, Location and WindowState to UserScopeSettings /// </summary> public class WindowSettings { #region Fields /// <summary> /// Register the "Save" attached property and the "OnSaveInvalidated" callback /// </summary> public static readonly DependencyProperty SaveProperty = DependencyProperty.RegisterAttached("Save", typeof (bool), typeof (WindowSettings), new FrameworkPropertyMetadata(OnSaveInvalidated)); private readonly Window mWindow; private WindowApplicationSettings mWindowApplicationSettings; #endregion Fields #region Constructors public WindowSettings(Window pWindow) { mWindow = pWindow; } #endregion Constructors #region Properties [Browsable(false)] public WindowApplicationSettings Settings { get { if (mWindowApplicationSettings == null) mWindowApplicationSettings = CreateWindowApplicationSettingsInstance(); return mWindowApplicationSettings; } } #endregion Properties #region Methods public static void SetSave(DependencyObject pDependencyObject, bool pEnabled) { pDependencyObject.SetValue(SaveProperty, pEnabled); } protected virtual WindowApplicationSettings CreateWindowApplicationSettingsInstance() { return new WindowApplicationSettings(this); } /// <summary> /// Load the Window Size Location and State from the settings object /// </summary> protected virtual void LoadWindowState() { Settings.Reload(); if (Settings.Location != Rect.Empty) { mWindow.Left = Settings.Location.Left; mWindow.Top = Settings.Location.Top; mWindow.Width = Settings.Location.Width; mWindow.Height = Settings.Location.Height; } if (Settings.WindowState != WindowState.Maximized) mWindow.WindowState = Settings.WindowState; } /// <summary> /// Save the Window Size, Location and State to the settings object /// </summary> protected virtual void SaveWindowState() { Settings.WindowState = mWindow.WindowState; Settings.Location = mWindow.RestoreBounds; Settings.Save(); } /// <summary> /// Called when Save is changed on an object. /// </summary> private static void OnSaveInvalidated(DependencyObject pDependencyObject, DependencyPropertyChangedEventArgs pDependencyPropertyChangedEventArgs) { var window = pDependencyObject as Window; if (window != null) if ((bool) pDependencyPropertyChangedEventArgs.NewValue) { var settings = new WindowSettings(window); settings.Attach(); } } private void Attach() { if (mWindow != null) { mWindow.Closing += WindowClosing; mWindow.Initialized += WindowInitialized; mWindow.Loaded += WindowLoaded; } } private void WindowClosing(object pSender, CancelEventArgs pCancelEventArgs) { SaveWindowState(); } private void WindowInitialized(object pSender, EventArgs pEventArgs) { LoadWindowState(); } private void WindowLoaded(object pSender, RoutedEventArgs pRoutedEventArgs) { if (Settings.WindowState == WindowState.Maximized) mWindow.WindowState = Settings.WindowState; } #endregion Methods #region Nested Types public class WindowApplicationSettings : ApplicationSettingsBase { #region Constructors public WindowApplicationSettings(WindowSettings pWindowSettings) { } #endregion Constructors #region Properties [UserScopedSetting] public Rect Location { get { if (this["Location"] != null) return ((Rect) this["Location"]); return Rect.Empty; } set { this["Location"] = value; } } [UserScopedSetting] public WindowState WindowState { get { if (this["WindowState"] != null) return (WindowState) this["WindowState"]; return WindowState.Normal; } set { this["WindowState"] = value; } } #endregion Properties } #endregion Nested Types } }
źródło
Domyślnym sposobem rozwiązania tego problemu jest użycie plików ustawień. Problem z plikami ustawień polega na tym, że musisz zdefiniować wszystkie ustawienia i napisać kod, który sam kopiuje dane tam iz powrotem. Dość uciążliwe, jeśli masz wiele nieruchomości do śledzenia.
Stworzyłem do tego dość elastyczną i bardzo łatwą w użyciu bibliotekę, po prostu powiedz jej, które właściwości obiektu śledzić, a ona zajmie się resztą. Możesz też skonfigurować bzdury, jeśli chcesz.
Biblioteka nazywa się Jot (github) , oto stary artykuł w CodeProject , który o niej napisałem.
Oto, jak można go użyć do śledzenia rozmiaru i lokalizacji okna:
public MainWindow() { InitializeComponent(); _stateTracker.Configure(this) .IdentifyAs("MyMainWindow") .AddProperties(nameof(Height), nameof(Width), nameof(Left), nameof(Top), nameof(WindowState)) .RegisterPersistTrigger(nameof(Closed)) .Apply(); }
Jot a pliki ustawień: Jot zawiera znacznie mniej kodu i jest dużo mniej podatny na błędy, ponieważ wystarczy wspomnieć o każdej właściwości tylko raz . W przypadku plików ustawień musisz wspomnieć o każdej właściwości 5 razy : raz, gdy jawnie utworzysz właściwość i dodatkowe cztery razy w kodzie, który kopiuje wartości w tę iz powrotem.
Pamięć masowa, serializacja itp. Są w pełni konfigurowalne. Ponadto, korzystając z IOC, możesz nawet podłączyć go tak, aby automatycznie stosował śledzenie do wszystkich obiektów, które rozwiązuje, tak że wszystko, co musisz zrobić, aby właściwość stała się trwała, to nałożyć na nią atrybut [Trackable].
Piszę to wszystko, bo uważam, że biblioteka jest na najwyższym poziomie i chcę o tym mówić.
źródło
Napisałem krótką lekcję, która to robi. Oto jak to się nazywa:
public MainWindow() { FormSizeSaver.RegisterForm(this, () => Settings.Default.MainWindowSettings, s => { Settings.Default.MainWindowSettings = s; Settings.Default.Save(); }); InitializeComponent(); ...
A oto kod:
public class FormSizeSaver { private readonly Window window; private readonly Func<FormSizeSaverSettings> getSetting; private readonly Action<FormSizeSaverSettings> saveSetting; private FormSizeSaver(Window window, Func<string> getSetting, Action<string> saveSetting) { this.window = window; this.getSetting = () => FormSizeSaverSettings.FromString(getSetting()); this.saveSetting = s => saveSetting(s.ToString()); window.Initialized += InitializedHandler; window.StateChanged += StateChangedHandler; window.SizeChanged += SizeChangedHandler; window.LocationChanged += LocationChangedHandler; } public static FormSizeSaver RegisterForm(Window window, Func<string> getSetting, Action<string> saveSetting) { return new FormSizeSaver(window, getSetting, saveSetting); } private void SizeChangedHandler(object sender, SizeChangedEventArgs e) { var s = getSetting(); s.Height = e.NewSize.Height; s.Width = e.NewSize.Width; saveSetting(s); } private void StateChangedHandler(object sender, EventArgs e) { var s = getSetting(); if (window.WindowState == WindowState.Maximized) { if (!s.Maximized) { s.Maximized = true; saveSetting(s); } } else if (window.WindowState == WindowState.Normal) { if (s.Maximized) { s.Maximized = false; saveSetting(s); } } } private void InitializedHandler(object sender, EventArgs e) { var s = getSetting(); window.WindowState = s.Maximized ? WindowState.Maximized : WindowState.Normal; if (s.Height != 0 && s.Width != 0) { window.Height = s.Height; window.Width = s.Width; window.WindowStartupLocation = WindowStartupLocation.Manual; window.Left = s.XLoc; window.Top = s.YLoc; } } private void LocationChangedHandler(object sender, EventArgs e) { var s = getSetting(); s.XLoc = window.Left; s.YLoc = window.Top; saveSetting(s); } } [Serializable] internal class FormSizeSaverSettings { public double Height, Width, YLoc, XLoc; public bool Maximized; public override string ToString() { using (var ms = new MemoryStream()) { var bf = new BinaryFormatter(); bf.Serialize(ms, this); ms.Position = 0; byte[] buffer = new byte[(int)ms.Length]; ms.Read(buffer, 0, buffer.Length); return Convert.ToBase64String(buffer); } } internal static FormSizeSaverSettings FromString(string value) { try { using (var ms = new MemoryStream(Convert.FromBase64String(value))) { var bf = new BinaryFormatter(); return (FormSizeSaverSettings) bf.Deserialize(ms); } } catch (Exception) { return new FormSizeSaverSettings(); } } }
źródło
Jest Nuget Projekt RestoreWindowPlace zobaczyć na github , że to wszystko dla Ciebie, zapisywania informacji w pliku XML.
Aby uruchomić go w oknie, wystarczy zadzwonić:
((App)Application.Current).WindowPlace.Register(this);
W aplikacji tworzysz klasę zarządzającą Twoimi oknami. Zobacz link do github powyżej, aby uzyskać więcej informacji.
źródło
Może ci się to spodobać:
public class WindowStateHelper { public static string ToXml(System.Windows.Window win) { XElement bounds = new XElement("Bounds"); if (win.WindowState == System.Windows.WindowState.Maximized) { bounds.Add(new XElement("Top", win.RestoreBounds.Top)); bounds.Add(new XElement("Left", win.RestoreBounds.Left)); bounds.Add(new XElement("Height", win.RestoreBounds.Height)); bounds.Add(new XElement("Width", win.RestoreBounds.Width)); } else { bounds.Add(new XElement("Top", win.Top)); bounds.Add(new XElement("Left", win.Left)); bounds.Add(new XElement("Height", win.Height)); bounds.Add(new XElement("Width", win.Width)); } XElement root = new XElement("WindowState", new XElement("State", win.WindowState.ToString()), new XElement("Visibility", win.Visibility.ToString()), bounds); return root.ToString(); } public static void FromXml(string xml, System.Windows.Window win) { try { XElement root = XElement.Parse(xml); string state = root.Descendants("State").FirstOrDefault().Value; win.WindowState = (System.Windows.WindowState)Enum.Parse(typeof(System.Windows.WindowState), state); state = root.Descendants("Visibility").FirstOrDefault().Value; win.Visibility = (System.Windows.Visibility)Enum.Parse(typeof(System.Windows.Visibility), state); XElement bounds = root.Descendants("Bounds").FirstOrDefault(); win.Top = Convert.ToDouble(bounds.Element("Top").Value); win.Left = Convert.ToDouble(bounds.Element("Left").Value); win.Height = Convert.ToDouble(bounds.Element("Height").Value); win.Width = Convert.ToDouble(bounds.Element("Width").Value); } catch (Exception x) { System.Console.WriteLine(x.ToString()); } } }
Po zamknięciu aplikacji:
Po uruchomieniu aplikacji:
źródło
Utwórz ciąg o nazwie WindowXml w ustawieniach domyślnych.
Użyj tej metody rozszerzenia w przypadku zdarzeń załadowania i zamknięcia okna, aby przywrócić i zapisać rozmiar i lokalizację okna.
using YourProject.Properties; using System; using System.Linq; using System.Windows; using System.Xml.Linq; namespace YourProject.Extensions { public static class WindowExtensions { public static void SaveSizeAndLocation(this Window w) { try { var s = "<W>"; s += GetNode("Top", w.WindowState == WindowState.Maximized ? w.RestoreBounds.Top : w.Top); s += GetNode("Left", w.WindowState == WindowState.Maximized ? w.RestoreBounds.Left : w.Left); s += GetNode("Height", w.WindowState == WindowState.Maximized ? w.RestoreBounds.Height : w.Height); s += GetNode("Width", w.WindowState == WindowState.Maximized ? w.RestoreBounds.Width : w.Width); s += GetNode("WindowState", w.WindowState); s += "</W>"; Settings.Default.WindowXml = s; Settings.Default.Save(); } catch (Exception) { } } public static void RestoreSizeAndLocation(this Window w) { try { var xd = XDocument.Parse(Settings.Default.WindowXml); w.WindowState = (WindowState)Enum.Parse(typeof(WindowState), xd.Descendants("WindowState").FirstOrDefault().Value); w.Top = Convert.ToDouble(xd.Descendants("Top").FirstOrDefault().Value); w.Left = Convert.ToDouble(xd.Descendants("Left").FirstOrDefault().Value); w.Height = Convert.ToDouble(xd.Descendants("Height").FirstOrDefault().Value); w.Width = Convert.ToDouble(xd.Descendants("Width").FirstOrDefault().Value); } catch (Exception) { } } private static string GetNode(string name, object value) { return string.Format("<{0}>{1}</{0}>", name, value); } } }
źródło
Używam odpowiedzi od Lance'a Clevelanda i wiążę Ustawienie. Ale używam więcej kodu, aby uniknąć tego, że moje okno jest poza ekranem.
private void SetWindowSettingsIntoScreenArea() { // first detect Screen, where we will display the Window // second correct bottom and right position // then the top and left position. // If Size is bigger than current Screen, it's still possible to move and size the Window // get the screen to display the window var screen = System.Windows.Forms.Screen.FromPoint(new System.Drawing.Point((int)Default.Left, (int)Default.Top)); // is bottom position out of screen for more than 1/3 Height of Window? if (Default.Top + (Default.Height / 3) > screen.WorkingArea.Height) Default.Top = screen.WorkingArea.Height - Default.Height; // is right position out of screen for more than 1/2 Width of Window? if (Default.Left + (Default.Width / 2) > screen.WorkingArea.Width) Default.Left = screen.WorkingArea.Width - Default.Width; // is top position out of screen? if (Default.Top < screen.WorkingArea.Top) Default.Top = screen.WorkingArea.Top; // is left position out of screen? if (Default.Left < screen.WorkingArea.Left) Default.Left = screen.WorkingArea.Left; }
źródło
Stworzyłem bardziej ogólne rozwiązanie w oparciu o genialną odpowiedź RandomEngys. Zapisuje pozycję do pliku w bieżącym folderze i nie musisz tworzyć nowych właściwości dla każdego nowego okna, które tworzysz. To rozwiązanie działa świetnie dla mnie z minimalnym kodem w kodzie.
using System; using System.IO; using System.Reflection; using System.Runtime.InteropServices; using System.Text; using System.Windows; using System.Windows.Interop; using System.Xml; using System.Xml.Serialization; namespace WindowPlacementNameSpace { // RECT structure required by WINDOWPLACEMENT structure [Serializable] [StructLayout(LayoutKind.Sequential)] public struct RECT { public int Left; public int Top; public int Right; public int Bottom; public RECT(int left, int top, int right, int bottom) { this.Left = left; this.Top = top; this.Right = right; this.Bottom = bottom; } } // POINT structure required by WINDOWPLACEMENT structure [Serializable] [StructLayout(LayoutKind.Sequential)] public struct POINT { public int X; public int Y; public POINT(int x, int y) { this.X = x; this.Y = y; } } // WINDOWPLACEMENT stores the position, size, and state of a window [Serializable] [StructLayout(LayoutKind.Sequential)] public struct WINDOWPLACEMENT { public int length; public int flags; public int showCmd; public POINT minPosition; public POINT maxPosition; public RECT normalPosition; } public static class WindowPlacement { private static readonly Encoding Encoding = new UTF8Encoding(); private static readonly XmlSerializer Serializer = new XmlSerializer(typeof(WINDOWPLACEMENT)); [DllImport("user32.dll")] private static extern bool SetWindowPlacement(IntPtr hWnd, [In] ref WINDOWPLACEMENT lpwndpl); [DllImport("user32.dll")] private static extern bool GetWindowPlacement(IntPtr hWnd, out WINDOWPLACEMENT lpwndpl); private const int SW_SHOWNORMAL = 1; private const int SW_SHOWMINIMIZED = 2; private static void SetPlacement(IntPtr windowHandle, string placementXml) { if (string.IsNullOrEmpty(placementXml)) { return; } byte[] xmlBytes = Encoding.GetBytes(placementXml); try { WINDOWPLACEMENT placement; using (MemoryStream memoryStream = new MemoryStream(xmlBytes)) { placement = (WINDOWPLACEMENT)Serializer.Deserialize(memoryStream); } placement.length = Marshal.SizeOf(typeof(WINDOWPLACEMENT)); placement.flags = 0; placement.showCmd = (placement.showCmd == SW_SHOWMINIMIZED ? SW_SHOWNORMAL : placement.showCmd); SetWindowPlacement(windowHandle, ref placement); } catch (InvalidOperationException) { // Parsing placement XML failed. Fail silently. } } private static string GetPlacement(IntPtr windowHandle) { WINDOWPLACEMENT placement; GetWindowPlacement(windowHandle, out placement); using (MemoryStream memoryStream = new MemoryStream()) { using (XmlTextWriter xmlTextWriter = new XmlTextWriter(memoryStream, Encoding.UTF8)) { Serializer.Serialize(xmlTextWriter, placement); byte[] xmlBytes = memoryStream.ToArray(); return Encoding.GetString(xmlBytes); } } } public static void ApplyPlacement(this Window window) { var className = window.GetType().Name; try { var pos = File.ReadAllText(Directory + "\\" + className + ".pos"); SetPlacement(new WindowInteropHelper(window).Handle, pos); } catch (Exception exception) { Log.Error("Couldn't read position for " + className, exception); } } public static void SavePlacement(this Window window) { var className = window.GetType().Name; var pos = GetPlacement(new WindowInteropHelper(window).Handle); try { File.WriteAllText(Directory + "\\" + className + ".pos", pos); } catch (Exception exception) { Log.Error("Couldn't write position for " + className, exception); } } private static string Directory => Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); } }
W swoim kodzie za Tobą dodaj te dwie metody
///This method is save the actual position of the window to file "WindowName.pos" private void ClosingTrigger(object sender, EventArgs e) { this.SavePlacement(); } ///This method is load the actual position of the window from the file protected override void OnSourceInitialized(EventArgs e) { base.OnSourceInitialized(e); this.ApplyPlacement(); }
w oknie XAML dodajesz to
Closing="ClosingTrigger"
źródło