.NET WPF Запомнить размер окна между сеансами

94

В основном, когда пользователь изменяет размер окна моего приложения, я хочу, чтобы приложение было того же размера, когда приложение снова открывается.

Сначала я решил обработать событие SizeChanged и сохранить высоту и ширину, но я думаю, что должно быть более простое решение.

Довольно простая проблема, но я не могу ее решить.

Даниил Харик
источник
2
Обратите внимание, что если вы восстанавливаете и размер, и положение (как это делается в большинстве примеров кода ниже), вы захотите обработать крайний случай, когда кто-то отключил монитор, на котором в последний раз было представлено окно, чтобы избежать представления вашего окно за кадром.
Омер Равив
@OmerRaviv Вы нашли пример, учитывающий крайний случай?
Эндрю Тракл,
У меня слишком мало репутации, чтобы добавить комментарий, поэтому я создал этот новый awnser. Я использую то же решение, что и Лэнс Кливленд, включая настройку РобДжонсона , но оно не работает, если вы используете его для дополнительных окон и хотите одновременно открывать больше из них ...
AelanY

Ответы:

122

Сохраните значения в файле user.config.

Вам нужно будет создать значение в файле настроек - оно должно находиться в папке «Свойства». Создайте пять значений:

  • Top типа double
  • Left типа double
  • Height типа double
  • Width типа double
  • Maximizedof type bool- удерживать независимо от того, развернуто ли окно или нет. Если вы хотите хранить больше информации, вам потребуется другой тип или структура.

Инициализируйте первые два значения 0, вторые два - размером по умолчанию вашего приложения, а последний - значением false.

Создайте обработчик событий Window_OnSourceInitialized и добавьте следующее:

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;
}

ПРИМЕЧАНИЕ. Установленное размещение окна должно происходить в исходном инициализированном событии окна, а не в конструкторе, иначе, если вы развернули окно на втором мониторе, оно всегда будет перезапускаться в максимальном размере на основном мониторе, и вы не сможете чтобы получить к нему доступ.

Создайте обработчик события Window_Closing и добавьте следующее:

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();

Это не удастся, если пользователь уменьшит область отображения - либо отключив экран, либо изменив разрешение экрана - в то время как приложение закрыто, поэтому вы должны добавить проверку, что желаемое местоположение и размер все еще действительны, прежде чем применять значения.

ChrisF
источник
5
Фактически, настройки с областью действия «Пользователь» сохраняются не в файле app.config в Program Files, а в файле user.config в каталоге данных приложения пользователя. Так что это не проблема ...
Томас Левеск
7
На самом деле вы можете добавить "WindowState" в настройки. Выберите тип -> обзор -> PresentationFramework -> System.Windows -> WindowState :)
Мартин Всетицка
2
FWIW, я также делаю это из обработчика изменения размера, в случае сбоя приложения. Они редко встречаются с обработкой необработанных исключений, но зачем наказывать пользователя потерей размера / местоположения, если они загадочно возникают.
Thomas
7
В этом коде есть ошибка в том, что, если пользователь открывает окно на своем втором экране, а затем отключает этот экран от компьютера, в следующий раз, когда они откроют окно, оно будет представлено за пределами экрана. Если окно модальное, пользователь вообще не сможет взаимодействовать с приложением и не поймет, что происходит. Вам необходимо добавить проверку границ с помощью Window.GetScreen () после преобразования координат экрана в значения, зависящие от DPI.
Омер Равив
2
@OmerRaviv - это не ошибка, а ограничение :) Серьезно - я не рассматривал этот аспект проблемы.
ChrisF
74

На самом деле вам не нужно использовать программный код для этого (кроме сохранения настроек). Вы можете использовать собственное расширение разметки, чтобы привязать размер и положение окна к таким настройкам:

<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}">

Вы можете найти код этого расширения разметки здесь: http://www.thomaslevesque.com/2008/11/18/wpf-binding-to-application-settings-using-a-markup-extension/

Томас Левеск
источник
4
Мне этот ответ нравится больше, чем выбранный принятый ответ. Отлично сработано.
Мосвальд,
6
+1 - Обожаю использование переплетов и расширений! Если вы добавите WindowState в свои привязанные настройки, он предоставит все возможности. В качестве альтернативы, если у вас есть пользовательские настройки, доступные в DataContext, вы можете использовать что-то вроде {Binding Settings.Height}и т. Д.
Мэтт ДеКри,
У этого подхода есть проблема, когда пользователь закрывает приложение, когда окно развернуто.
Vinicius Rocha
@Vinicius, ты можешь уточнить? В чем именно проблема?
Thomas Levesque
4
Как насчет того, когда у людей есть два монитора, и, следовательно, у них могут быть отрицательные координаты, а затем они меняют конфигурации монитора, и значения больше не действительны?
Эндрю Тракл
33

Хотя вы можете «свернуть свой собственный» и вручную где-нибудь сохранить настройки, и в целом это сработает, очень легко не справиться со всеми случаями правильно. Гораздо лучше позволить ОС делать всю работу за вас, вызывая GetWindowPlacement () при выходе и SetWindowPlacement () при запуске. Он обрабатывает все безумные крайние случаи, которые могут возникнуть (несколько мониторов, сохранить нормальный размер окна, если оно закрыто при максимальном увеличении, и т. Д.), Так что вам не нужно.

В этом образце MSDN показано, как использовать их с приложением WPF. Образец не идеален (при первом запуске окно будет начинаться в верхнем левом углу настолько маленьким, насколько это возможно, и при сохранении значения типа конструктором настроек наблюдается странное поведение WINDOWPLACEMENT), но он должен, по крайней мере, помочь вам начать работу.

Энди
источник
Хорошее решение. Однако я только что обнаружил, что GetWindowPlacement / SetWindowPlacement не
Марк Белл
1
@RandomEngy опубликовал улучшенный ответ на основе этого.
Стефан Гуришон
27

Привязка "длинной формы", которую Томас опубликовал выше, почти не требует кодирования, просто убедитесь, что у вас есть привязка пространства имен:

<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}">

Затем, чтобы сэкономить на коде программной части:

private void frmMain_Closed(object sender, EventArgs e)
{
    Properties.Settings.Default.Save();
}
Лэнс Кливленд
источник
Я выбрал это решение, но сохранил настройки только в том случае, если состояние окна было нормальным, в противном случае его может быть неудобно вывести из максимального режима
Дэвид Сайкс
7
+1 Я тоже использовал это, @DavidSykes - Добавление еще одного параметра для состояния окна, кажется, работает достаточно хорошо, напримерWindowState="{Binding Source={x:Static properties:Settings.Default}, Path=WindowState, Mode=TwoWay}"
Роб Джонсон
@RobJohnson Я попробовал ваше предложение, и оно сработало очень хорошо, спасибо.
Дэвид Сайкс
4

В качестве альтернативы вам также может понравиться следующий подход ( см. Источник ). Добавьте класс WindowSettings в свой проект и вставьте WindowSettings.Save="True"в заголовок главного окна:

<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">

Где WindowSettings определяется следующим образом:

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
}
}
Эрик Вуллингс
источник
3

По умолчанию способ решения - использование файлов настроек. Проблема с файлами настроек заключается в том, что вам нужно определить все настройки и написать код, который сам копирует данные туда и обратно. Довольно утомительно, если вам нужно отслеживать много объектов недвижимости.

Я сделал для этого довольно гибкую и очень простую в использовании библиотеку, вы просто указываете ей, какие свойства какого объекта нужно отслеживать, а он делает все остальное. Вы тоже можете настроить из него хрень, если хотите.

Библиотека называется Jot (github) , вот старая статья CodeProject, которую я написал об этом.

Вот как вы могли бы использовать его для отслеживания размера и местоположения окна:

public MainWindow()
{
    InitializeComponent();

    _stateTracker.Configure(this)
        .IdentifyAs("MyMainWindow")
        .AddProperties(nameof(Height), nameof(Width), nameof(Left), nameof(Top), nameof(WindowState))
        .RegisterPersistTrigger(nameof(Closed))
        .Apply();
}

Jot vs. файлы настроек: с Jot значительно меньше кода, и он намного меньше подвержен ошибкам, поскольку вам нужно упомянуть каждое свойство только один раз . В файлах настроек вам нужно упомянуть каждое свойство 5 раз : один раз, когда вы явно создаете свойство, и еще четыре раза в коде, который копирует значения туда и обратно.

Хранение, сериализация и т. Д. Полностью настраиваются. Кроме того, при использовании IOC вы даже можете подключить его, чтобы он автоматически применял отслеживание ко всем объектам, которые он разрешает, так что все, что вам нужно сделать, чтобы сделать свойство постоянным, - это добавить к нему атрибут [Trackable].

Я пишу все это, потому что считаю, что библиотека на высшем уровне, и хочу рассказать об этом.

анакич
источник
Хорошо, спасибо за это - я использовал ваш фрагмент кода в новом классе, чтобы настроить трекер состояния с путем, основанным на имени программы. С этого момента мне нужно написать только одну строку, и все свойства окна обрабатываются
Awesomeness
1

Я написал быстрый класс, который делает это. Вот как это называется:

    public MainWindow()
    {
        FormSizeSaver.RegisterForm(this, () => Settings.Default.MainWindowSettings,
                                   s =>
                                   {
                                       Settings.Default.MainWindowSettings = s;
                                       Settings.Default.Save();
                                   });
        InitializeComponent();
        ...

А вот код:

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();
        }
    }
}
tster
источник
window.Intitialized должно быть window.Loaded см. в основномtech.blogspot.com/
Глеб Севрук
@ Глеб, думаю оба работают. У вас проблемы с ним на Initialized?
tster 09
Да, поскольку развернутое окно будет отображаться на неправильном экране, если вы используете только инициализированное событие. То, что я сделал, и это, похоже, работает: теперь я также подписываюсь на событие Loaded. Я переместил _window.WindowState = s.Maximized? WindowState.Maximized: WindowState.Normal; строка внутри обработчика события «Loaded». window.Initialized + = InitializedHandler; window.Loaded + = LoadedHandler; кстати: мне нравится такой подход
Глеб Севрук
1

Там в NuGet проекта RestoreWindowPlace увидеть на GitHub , что делает все это для вас, сохраняя информацию в файле XML.

Чтобы заставить его работать с окном, достаточно просто позвонить:

((App)Application.Current).WindowPlace.Register(this);

В приложении вы создаете класс, который управляет вашими окнами. См. Ссылку на github выше для получения дополнительной информации.

Чак Сэвидж
источник
0

Вам может понравиться это:

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());
        }
    }
}

Когда приложение закрывается:

        Properties.Settings.Default.Win1Placement = WindowStateHelper.ToXml(win1);
        Properties.Settings.Default.Win2Placement = WindowStateHelper.ToXml(win2);
        ...

Когда приложение запускается:

        WindowStateHelper.FromXml(Properties.Settings.Default.Win1Placement, win1);
        WindowStateHelper.FromXml(Properties.Settings.Default.Win2Placement, win2);
        ...
Павел
источник
0

Создайте строку с именем WindowXml в настройках по умолчанию.

Используйте этот метод расширения для событий Window Loaded и Closing, чтобы восстановить и сохранить размер и местоположение окна.

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);
        }
    }
}
Tempeck
источник
0

Я использую ответ Ланса Кливленда и связываю Настройки. Но я использую еще код, чтобы мое окно не выходило за пределы экрана.

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;
}
Маркус
источник
0

Я сделал более общее решение, основанное на блестящем ответе RandomEngys. Он сохраняет позицию в файл в текущей папке, и вам не нужно создавать новые свойства для каждого нового окна, которое вы создаете. Это решение отлично работает для меня с минимальным кодом в коде.

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);

    }
}

В вашем коде вы добавляете эти два метода

///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();
}

в окне xaml вы добавляете это

Closing="ClosingTrigger"
Bjorn
источник