Caricamento di una finestra WPF senza mostrarla
Domanda
creo un tasto di scelta rapida globale per mostrare una finestra PInvoking RegisterHotKey()
. Ma per fare questo ho bisogno HWND
di quella finestra, che non esiste finché non viene caricata la finestra, che significa mostrato per la prima volta. Ma io non voglio mostrare la finestra prima di poter impostare il tasto di scelta rapida. Esiste un modo per creare un HWND
per quella finestra che è invisibile per l'utente?
Soluzione
Se si prendono di mira .NET 4.0 si può fare uso del nuovo metodo EnsureHandle
disponibili sul WindowInteropHelper
:
public void InitHwnd()
{
var helper = new WindowInteropHelper(this);
helper.EnsureHandle();
}
(grazie a Thomas Levesque per puntualizzazione. )
Se si prendono di mira una versione precedente di .NET Framework, il modo più semplice è quello di mostrare la finestra per raggiungere la HWND durante l'impostazione alcune proprietà per assicurarsi che la finestra è invisibile e non rubare messa a fuoco:
var window = new Window() //make sure the window is invisible
{
Width = 0,
Height = 0,
WindowStyle = WindowStyle.None,
ShowInTaskbar = false,
ShowActivated = false
};
window.Show();
Una volta che si desidera visualizzare la finestra attuale è quindi possibile impostare il contenuto, le dimensioni e cambiare lo stile di nuovo a una finestra normale.
Altri suggerimenti
È inoltre possibile modificare la finestra in una cosiddetta finestra di messaggio-solo. Poiché questo tipo di finestra non supporta gli elementi grafici non sarà mai mostrato. In sostanza si tratta di chiamare:
SetParent(hwnd, (IntPtr)HWND_MESSAGE);
In entrambi creare una finestra messaggio dedicato che sarà sempre nascosto, o utilizzare la finestra vera e propria GUI e cambiare di nuovo a una finestra normale quando si desidera visualizzarlo. Vedere il codice qui sotto per un esempio più completo.
[DllImport("user32.dll")]
static extern IntPtr SetParent(IntPtr hwnd, IntPtr hwndNewParent);
private const int HWND_MESSAGE = -3;
private IntPtr hwnd;
private IntPtr oldParent;
protected override void OnSourceInitialized(EventArgs e)
{
base.OnSourceInitialized(e);
HwndSource hwndSource = PresentationSource.FromVisual(this) as HwndSource;
if (hwndSource != null)
{
hwnd = hwndSource.Handle;
oldParent = SetParent(hwnd, (IntPtr)HWND_MESSAGE);
Visibility = Visibility.Hidden;
}
}
private void OpenWindowMenuItem_Click(object sender, RoutedEventArgs e)
{
SetParent(hwnd, oldParent);
Show();
Activate();
}
Per me la soluzione di impostare la larghezza, l'altezza a zero e lo stile a nessuno, non ha funzionato, in quanto mostrava ancora una piccola finestra, con un'ombra di fastidioso di quello che sembra essere il bordo intorno una finestra 0x0 (testati su Windows 7). Perciò sto fornendo questa opzione alternativa.
Questo è un trucco sporco, ma dovrebbe funzionare, e non ha gli aspetti negativi di cambiare l'opacità:
- impostare il
WindowStartupLocation
aManual
- impostare le proprietà
Top
eLeft
a qualche parte di fuori dello schermo - set
ShowInTaskbar
su false in modo che l'utente non si rende conto c'è una nuova finestra -
Show
eHide
la finestra
Si dovrebbe ora essere in grado di recuperare il HWND
EDIT: un'altra opzione, probabilmente meglio: impostare ShowInTaskBar
su false e WindowState
a Minimized
, poi mostrarlo: non sarà visibile a tutti
avevo già pubblicato in risposta a questa domanda, ma ho appena trovato una soluzione migliore.
Se avete solo bisogno di assicurarsi che l'HWND è creato, senza in realtà che mostra la finestra, si può fare questo:
public void InitHwnd()
{
var helper = new WindowInteropHelper(this);
helper.EnsureHandle();
}
(in realtà il metodo EnsureHandle
non era disponibile quando la questione è stata pubblicata, è stato introdotto in .NET 4.0)
Non ho mai provato a fare quello che stai facendo, ma se avete bisogno di mostrare la finestra per ottenere il HWND, ma non si vuole Mostra è, impostare l'opacità della finestra a 0 . Questo sarà anche evitare di rilevamento del contatto si verifichi. Poi si potrebbe avere un metodo pubblico sulla finestra per cambiare l'opacità al 100 quando si vuole renderlo visibile.
so assolutamente nulla di WPF, ma potreste creare un messaggio unica finestra con altri mezzi (PInvoke per esempio) per ricevere il messaggio WM_HOTKEY? Se sì, allora una volta che si riceve il WM_HOTKEY, si potrebbe avviare la finestra WPF da lì.
La classe WindowInteropHelper dovrebbe permettere di ottenere il HWND per la finestra WPF.
MyWindow win = new MyWindow();
WindowInteropHelper helper = new WindowInteropHelper(win);
IntPtr hwnd = helper.Handle;
Un'altra opzione in modo simile a impostare l'opacità a 0, è quello di impostare la dimensione di 0 e impostare la posizione di essere fuori dallo schermo. Questo non richiederà l'AllowsTransparency = true.
Ricorda inoltre che una volta che avete dimostrato una volta è possibile nasconderlo e ancora ottenere il hwnd.
Fare la dimensione della finestra 0 x 0 px, messo ShowInTaskBar false, mostrarlo, quindi ridimensionare quando necessario.
Ho creato metodo di estensione per mostrare finestra invisibile, chiamate Show
prossimi si comporteranno OK.
public static class WindowHelper
{
public static void ShowInvisible(this Window window)
{
// saving original settings
bool needToShowInTaskbar = window.ShowInTaskbar;
WindowState initialWindowState = window.WindowState;
// making window invisible
window.ShowInTaskbar = false;
window.WindowState = WindowState.Minimized;
// showing and hiding window
window.Show();
window.Hide();
// restoring original settings
window.ShowInTaskbar = needToShowInTaskbar;
window.WindowState = initialWindowState;
}
}
Ho notato che l'ultima cosa che si verifica quando la finestra viene inizializzato, è il cambiamento di WindowState
, se diversa dal normale. Così, si può effettivamente fare uso di esso:
public void InitializeWindow(Window window) {
window.Top = Int32.MinValue;
window.Left = Int32.MinValue;
window.Width = 0;
window.Height = 0;
window.ShowActivated = false;
window.ShowInTaskbar = false;
window.Opacity = 0;
window.StateChanged += OnBackgroundStateChanged;
window.WindowStyle = WindowStyle.None;
}
public void ShowWindow(Window window) {
window.Show();
window.WindowState = WindowState.Maximized;
}
protected bool isStateChangeFirst = true;
protected void OnBackgroundStateChanged(object sender, EventArgs e) {
if (isStateChangeFirst) {
isStateChangeFirst = false;
window.Top = 300;
window.Left = 200;
window.Width = 760;
window.Height = 400;
window.WindowState = WindowState.Normal;
window.ShowInTaskbar = true;
window.Opacity = 1;
window.Activate();
}
}
Questo funziona abbastanza giusto per me. E non richiede di lavorare con qualsiasi maniglie e roba, e, soprattutto, non richiede di avere una classe personalizzata per una finestra. Che è grande per XAML caricato dinamicamente. Ed è anche un ottimo modo se si stanno facendo un'applicazione a schermo intero. Non c'è nemmeno bisogno di cambiare il suo stato di nuovo al normale o impostare la larghezza e l'altezza corretta. Basta andare con il
protected bool isStateChangeFirst = true;
protected void OnBackgroundStateChanged(object sender, EventArgs e) {
if (isStateChangeFirst) {
isStateChangeFirst = false;
window.ShowInTaskbar = true;
window.Opacity = 1;
window.Activate();
}
}
E il gioco è fatto.
E anche se mi sbaglio nel mio presupposto che il cambiamento di stato è l'ultima cosa fatta quando viene caricata la finestra, è ancora possibile modificare in qualsiasi altro caso, non ha molta importanza.