[WPF]如何偵測視窗移動LocationChanged事件結束

開發視窗程式時,常常會需要記住視窗最後的位置,方便使用者下次開啟時,位置和上次相同,不需要再重新拖拉視窗

一般的做法是,在離開的時候才儲存位置,正常這樣的做法是OK的,不過我的狀況有點特殊,使用者可能不會乖乖地離開,狀況不一定,所以我希望在視窗拖拉結束時儲存位置

WPF有提供LocationChanged事件,讓你可以視窗拖拉時觸發,不過卻不曉得何時拖拉會結束,有時候系統沒提供事件就很難處理,必須自己用較消耗資源的方式實現

在google過後發現stackoverflow上有一篇和我相同的問題,WPF Window LocationChanged ended,其中最佳解答只提供了做法描述,並沒有實際執行的程式碼可以參考,如果我還是新手時,可能就直接關掉視窗另尋他路了吧,不過仔細看過其實實作不難,它提供了兩種做法

  1. 設置一個timer,間隔幾百毫秒,在第一次LocationChanged事件時讓timer開始計時,之後LocationChanged就重新計時,不讓timer觸發事件,等到timer觸發時,就可以假設使用者已經停止拖拉了,缺點是需要額外一個timer消耗資源
  2. 使用window message hook來監聽WM_NCLBUTTONUP事件

第2種做法看起來比較好,不過實作後發現沒法偵測到WM_NCLBUTTONUP,google後似乎有許多種原因,就暫不深究,採用第1種方式,雖然要一個timer,但timer實際運作時間也只有拖拉時和放開後的幾百毫秒,以下就來說明怎麼做吧

首先在window上添加LocationChanged事件: Window_LocationChanged

<Window x:Class="GateControl.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:GateControl"
        Title="GateControl" Width="800" Height="600"
        LocationChanged="Window_LocationChanged"></Window>

接著設置timer和判斷用的變數,在程式開始時,讀取並設置視窗位置

//判斷視窗是否正在移動
bool isMovingWindow = false;
Timer timerSaveWindowPosition;

public MainWindow()
{
    InitializeComponent();
    //如果在window的Loaded事件才寫入,會看到視窗從舊位置跳到新位置
    Left = Properties.Settings.Default.left;
    Top = Properties.Settings.Default.top;
    timerSaveWindowPosition = new Timer(500);
    timerSaveWindowPosition.Elapsed += (o, a) =>
    {
        //放開拖拉後,會先將isMovingWindow寫入false,之後才會觸發儲存視窗位置
        if (!isMovingWindow)
        {
            Dispatcher.BeginInvoke(new Action(() => 
            {
                Properties.Settings.Default.left = Left;
                Properties.Settings.Default.top = Top;
                Properties.Settings.Default.Save();
            }));
            Console.WriteLine("save position");
            //任務完成,記得停用timer,避免一直儲存消耗資源
            timerSaveWindowPosition.Stop();
        }
        //即使移動中觸發了timer,還是被改寫成true,只有在靜止時,才會停留在false
        isMovingWindow = false;
    }
}

private void Window_LocationChanged(object sender, EventArgs e)
{
    //由靜止開始移動時,讓timer開始計時
    if (!isMovingWindow)
    {
        timerSaveWindowPosition.Start();
    }
    //寫入視窗正在移動
    isMovingWindow = true;
}

看起來是不是很簡單呢,相同的原理其實也可以用在其他地方,像是視窗大小拖拉結束等這類只有移動時才有事件的動作

參考來源

發表迴響

在下方填入你的資料或按右方圖示以社群網站登入:

WordPress.com 標誌

您的留言將使用 WordPress.com 帳號。 登出 /  變更 )

Google photo

您的留言將使用 Google 帳號。 登出 /  變更 )

Twitter picture

您的留言將使用 Twitter 帳號。 登出 /  變更 )

Facebook照片

您的留言將使用 Facebook 帳號。 登出 /  變更 )

連結到 %s

Create a website or blog at WordPress.com

向上 ↑

%d 位部落客按了讚: