Witaj na Zine.net online Zaloguj się | Rejestracja | Pomoc

Artur Stanaszek

Moje przygody z .NET

Subskrypcje

(WinForm) Rysowanie grafiki na oknie MDIParent

Zadanie: Narysować obrazek (np. logo) w prawym, dolnym rogu okna MdiParent. Oczywiście narysować, oznacza, że ten obrazek ma być niewrażliwy na zmianę rozmiaru okna, przesuwanie okien MdiChild, dokowanie okien itp. Cały czas ma być w rogu obszaru, dostępnego dla okien MdiChild. Standardowe możliwości rysowania grafiki w tle (System.Windows.Forms.ImageLayout) na niewiele nam się przydarzą, a dokładnie, wcale.

1) Rysowanie na formularzu, który ma ustawiony IsMdiParent = false.

Tu sprawa jest dość prosta, piszemy funkcję obsługi zdarzenia paint dla formularza:

private void FormLogoBase_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
{
this.PaintImage(e.Graphics);
}

Lub nadpisujemy metodę OnPaint formularza:

protected override void OnPaint(PaintEventArgs e)
{
PaintImage(e.Graphics);
}

I odpowiednią funkcję rysowania obrazka, np.:

private void PaintImage(Graphics g)
{
Bitmap bit = WinForms.Properties.Resources.logo;
int x = this.ClientRectangle.Width - bit.Width - 80;
int y = this.ClientRectangle.Height - bit.Height - 80;
g.DrawImageUnscaled(bit, x, y);
}

W konstruktorze formularza należy jeszcze dopisać:

SetStyle(System.Windows.Forms.ControlStyles.ResizeRedraw, true);

Bo przy zmianie rozmiaru okna, chcemy narysować zawartość formularza ponownie.

I prawie działa. Prawie, bo ktoś może zapytać:

2) A czemu nie widzę tego w DesignTime?

Jeśli zależy nam nam, żeby widzieć nasze logo trybie projektowania formularza, musimy wiedzieć, że nasze VS nie wykonuje kodu znajdującego się w OnPaint, nie ‘odpala’ zdarzeń paint, tak jak i nie wykonuje kodu zawartego w konstruktorze formularza. Wyświetlenie formularza w trybie projektowania polega na wykonaniu kodu znajdującego się w InitializeComponent. Jeśli zmienimy nazwę tej funkcji, to zobaczymy pusty formularz. Nasze IDE oszczędza cenne zasoby i nie marnuje czasu na wykonanie zbędnych funkcji (nie mam zdania czy słusznie). Trzeba je trochę oszukać. Stwórzmy formularz bazowy FormBase i do niego przenieśmy kod z pkt.1, a właściwy formularz niech po nim dziedziczy. Teraz przechytrzyliśmy VS, bo kod rysowaniu w formularzach nadrzędnych do aktualnie wyświetlanego, jest wykonywany (podobnie jak kod w konstruktorze).

3) Zmieniam na IsMDIContainer = true i zonk.

Jeśli zastosujemy dotychczas opisany kod, dla formularza MdiParent, to nie zobaczymy naszego obrazka. Podczas uruchomienia programu, do kolekcji Controls formularza, dodawany jest obiekt typu MDIClient. Dokładnie dodawany jest, po wykonaniu linii:

IsMdiContainer = true; //(w InitializeComponent)

Dlatego, żeby ustawić referencję na ten obiekt, możemy nadpisać właściwość IsMDIContainer:

[DefaultValue(false)]
public new bool IsMdiContainer
{
get { return base.IsMdiContainer; }
set
{
base.IsMdiContainer = value;
if (!value)
{
return;
}

foreach (Control ctrl in this.Controls)
{
MdiClient client = ctrl as MdiClient;
if (client != null)
{
mdiClient = client;
ControlStyles styles = ControlStyles.DoubleBuffer;
try
{
Type mdiType = typeof(MdiClient);
System.Reflection.BindingFlags flags = System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance;
System.Reflection.MethodInfo method = mdiType.GetMethod("SetStyle", flags);
object[] param = { styles, true };
method.Invoke(mdiClient, param);
}
catch
{
}

mdiClient.Paint += new PaintEventHandler(this.MdiClient_Paint);
break;
}
}
}
}

(Moglibyśmy przeczesać kolekcję Controls w OnPaint, ale tak przy okazji ustawiliśmy DoubleBuffer)

A w formularzu definiujemy:

MdiClient mdiClient = null;

W zdarzeniu paint obiektu mdiClient będziemy mogli rysować na obszarze dostępnym dla formularzy MdiChild.

Obiekt MdiClient, jest dokładnie takiego rozmiaru jak widoczny obszar formularza głównego, pozostawiony dla okien MdiChild (nie dokowanych). Czyli pomniejszony o menu, zadokowane okna, StatusBary, CaptionBox itp. Dlatego możemy trochę ulepszyć naszą funkcję rysowania obrazka:

private void PaintImage(Graphics g)
{
Bitmap bit = WinForms.Properties.Resources.logo;
int x = this.ClientRectangle.Width - bit.Width - 80;
int y = this.ClientRectangle.Height - bit.Height - 80;
if (mdiClient != null)
{
x = mdiClient.Width - bit.Width - 80;
y = mdiClient.Height - bit.Height - 80;
}

g.DrawImageUnscaled(bit, x, y);
}

Po uruchomieniu możemy zobaczyć nasze logo. Teraz wracamy (znowu) do pytania z pkt2. Nie widzimy tego w designtime. Należy dopisać w formularzu bazowym:

protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
this.PaintImage(e.Graphics);
}

4) Problem scrolla.

Jeśli ruszamy paskiem przewijania (pojawia się gdy formularze typu MdiChild nie mieszczą się w obszarze MdiClient) to nasze logo „pływa” razem z paskiem, zamiast „trzymać” się swojego rogu. Musimy wywołać:

mdiClient.Invalidate();

Tylko kiedy, jak nie mamy zdarzenia scrolla dla MdiClient (nic tu się nie zmieniło od .NET 1.1). Pierwsza moja próba, polegała na obsłudze zdarzenia LocationChanged każdego MdiChild. Jest to o tyle nieszczęśliwe rozwiązanie, że powoduje skoki obrazka po ekranie. Najpierw przesuwa się on razem z pozostałymi oknami, a potem wraca na swoje miejsce (w rogu).

Pozostaje nam nasłuchiwać komunikaty (z frontu) i czekać na te dotyczące scrolla.

Staram się nie używam poniższych sztuczek, ale w tym wypadku nie widzę innego sposobu. W formularzu bazowym definiujemy klasę prywatną:

private class SubClassMdiClient : NativeWindow
{
private const int WM_HSCROLL = 0x114;
private const int WM_VSCROLL = 0x115;
private const int WM_SIZE = 0x0005;
private const int WM_RESIZE = 0x0085;
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_HSCROLL || m.Msg == WM_VSCROLL || m.Msg == WM_RESIZE)
{
MdiClient mdiClient = Control.FromHandle(this.Handle) as MdiClient;
if (mdiClient != null)
{
mdiClient.Invalidate();
}
}
else
{
Console.WriteLine(m.Msg.ToString("X"));
}

base.WndProc(ref m);
}
}

Definiujemy zmienną:

private SubClassMdiClient subMdiClient = new SubClassMdiClient();

I dopisujemy obsługę zdarzenia Load:

private void FormLogoBase_Load(object sender, EventArgs e)
{
try
{
if (mdiClient != null)
{
this.subMdiClient.AssignHandle(mdiClient.Handle);
}
}
catch
{
}
}

Teraz scroll nam niegroźny. Chciałbym napisać, że w ten sposób załatwimy wszystkie problemy związane z postawionym na początku zdaniem, ale to nieprawda. Wiele problemów generują kontrolki, które zmieniają swój rozmiar gdy są zadokowane i robią to a’la animacja (np. NavBar-y) zdarzenie zmiany rozmiaru MdiClient jest wywoływane po zakończeniu zmiany rozmiaru takiej kontrolki. Taki przypadek trzeba obsłużyć indywidualnie i wywołać mdiClient.Invalidate() w zdarzeniu Resize kontrolki sprawiającej problem.

Pisząc ten wpis korzystałem z artykułu:

www.codeproject.com/KB/cs/mdiclientrevisited.aspx

Opublikowane 19 lutego 2009 20:36 przez arturstan

Filed under:

Komentarze:

Brak komentarzy

Komentarze anonimowe wyłączone