Announcement

Collapse
No announcement yet.

PopupMenu andersfarbig

Collapse
X
  • Filter
  • Time
  • Show
Clear All
new posts

  • PopupMenu andersfarbig

    kennt jemand eine Möglichkeit, ein PopupMenu andersfarbig darzustellen.
    Danke für eine Nachricht.
    Manfred Abeln

  • #2
    Jo, steht im aktuellen PC MAGAZIN. Auf Wunsch tipp ich´s ab. ABER: wenn man Bildchen im Popupmenü benutzt, dann werden die prompt nicht mehr dargestellt. Darf ich an der Stelle mal fragen, wie man das umgehen, bzw. wiederherstellen könnte?

    Gruß.
    Mathias

    Comment


    • #3
      Ich habe mal selbst ein bisschen experimentiert (wieder mal), und auch bei anderen Leuten geschaut, wie die das so machen (wieder mal).
      Also: Grundlage ist der entsprechende Beitrag im PC MAGAZIN 9/2001, der beschreibt, wie man einen Menüeintrag umfärben kann. Über Google fand ich ´ne Seite aus CodeCentral, wo jemand für C so ein Menü geschrieben hat. Dank meiner (mittlerweile doch besseren) JavaScript- und Perl-Kenntnisse habe ich den C-Code diesmal verstanden und konnte den für mich wichtigen Teil (Bitmaps!) in Delphi umsetzen.
      Und da wäre es:

      <PRE><CODE>
      //
      // eigentlich per externer Datei änderbar, aber zum Vorzeigen als Konstanten
      //
      const
      pop_font_disabled = $00a6afb7;
      pop_font = clBlack;
      pop_bg = $00f8f9f9;
      pop_left_bar = $00d8dbde;
      pop_font_selected = clBlack;
      pop_bg_selected = $00d9b3b3;
      pop_frame_selected = $00800000;

      procedure TPopEyh5.CustomPopupDraw(Sender: TObject; ACanvas: TCanvas; ARect: TRect; State: TOwnerDrawState; SubMenu: boolean);
      var
      xMI : TMenuItem;
      Glyph : TBitmap;
      tPos, lPos: integer;
      Text : string;
      begin
      xMI := (Sender as TMenuItem);

      //
      // Selektiert?
      //
      if(odSelected in State) and ((not(odDisabled in State)) and (not(odGrayed in State))) then
      begin
      ACanvas.Brush.Color := pop_frame_selected; ACanvas.FrameRect(ARect); // ´nen Rahmen um den Menüpunkt ´rum
      inc(ARect.Left); inc(ARect.Top); dec(ARect.Right); dec(ARect.Bottom); // ein bissel kleiner machen
      ACanvas.Brush.Color := pop_bg_selected; ACanvas.FillRect(ARect); // füllen
      end
      else
      begin
      ACanvas.Brush.Color := pop_bg; ACanvas.FillRect(ARect);
      //
      // wenn es kein Untermenü ist, dann einen WordXP-typischen grauen Balken an die linke Seite
      // 1. hier beschränkt auf 20 Pixel Breite; lässt sich aber ändern
      // 2. auch wenn´s nicht mehr originalgetreu ist -- ich finde, im Untermenü passt der Balken nicht
      // deswegen die Einschränkung
      //
      if(not(SubMenu)) then
      begin
      lPos := ARect.Right; ARect.Right := 20;
      ACanvas.Brush.Color := pop_left_bar;
      ACanvas.FillRect(ARect);
      ARect.Right := lPos;
      ACanvas.Brush.Color := pop_bg; // reset bg color
      end;
      end;
      //
      // mit Check-Häkchen?
      //
      if(odChecked in State) then
      begin
      //
      // die Idee ist aus Borlands "menus.pas"
      // (man muss nicht alles wissen -- man muss wissen, wo man´s finden kann ;o)
      //
      Glyph := TBitmap.Create;
      try
      Glyph.Handle := LoadBitmap(0,pchar(OBM_CHECK));
      if(odSelected in State) and ((not(odDisabled in State)) and (not(odGrayed in State))) then ACanvas.Draw(ARect.Left + 3, ARect.Top + 2,Glyph)
      else ACanvas.Draw(succ(ARect.Left) + 3, succ(ARect.Top) + 2,Glyph);
      finally
      Glyph.free;
      end;
      end
      </CODE></PRE&gt

      Comment


      • #4
        <PRE><CODE>
        //
        // ... oder ´ner Bitmap?
        //
        else if(xMI.ImageIndex <> -1) then
        begin
        Glyph := TBitmap.Create;
        try
        PopupImg.GetBitmap(xMI.ImageIndex,Glyph); // "PopupImg" ist meine ImageList
        Glyph.Transparent := true;
        Glyph.Width := 16; Glyph.Height := 16; // beschränken auf 16x16 (kann man ja auch ändern)

        if(odSelected in State) and ((not(odDisabled in State)) and (not(odGrayed in State))) then ACanvas.Draw(pred(ARect.Left) + 2, pred(ARect.Top) + 1,Glyph)
        else ACanvas.Draw(ARect.Left + 2, ARect.Top + 1,Glyph);
        finally
        Glyph.free;
        end;
        end;

        //
        // Und der Text:
        //
        Text := xMI.Caption; if(pos('&',Text) > 0) then delete(Text,pos('&',Text),1);
        if(Text = '-') then // separator
        begin
        tPos := ARect.Top + ((ARect.Bottom - ARect.Top) div 2);
        ACanvas.Pen.Color := pop_font_disabled;
        if(SubMenu) then ACanvas.MoveTo(ARect.Left + 2,tPos)
        else ACanvas.MoveTo(24,tPos);
        ACanvas.LineTo(pred(ARect.Right),tPos);
        end
        else // text
        begin
        tPos := ARect.Top + (ARect.Bottom - ARect.Top - ACanvas.TextHeight('W')) div 2;
        lPos := 24;
        if(odDisabled in State) or (odGrayed in State) then ACanvas.Font.Color := pop_font_disabled
        else if(odSelected in State) then ACanvas.Font.Color := pop_font_selected
        else ACanvas.Font.Color := pop_font;
        ACanvas.TextOut(lPos,tPos,Text);
        end;
        end;

        procedure TPopEyh5.mnuAddUserAdvancedDrawItem(Sender: TObject; ACanvas: TCanvas; ARect: TRect; State: TOwnerDrawState);
        begin
        self.CustomPopupDraw(Sender,ACanvas,ARect,State,fa lse);
        end;
        </CODE></PRE>

        Das Beispiel zeigt die neue interne Funktion und den Aufruf eines Menüpunktes, den man setzen sollte. Logo muss die Eigenschaft "OwnerDraw" des Popup-Menüs auf true gesetzt werden, aber witzigerweise funktioniert´s auch ohne. Warum auch immer ...

        Was mich jetzt noch interessieren würde, wäre die Sache mit den Hotkeys, sprich: unterstrichenem Text. Bei ´nem Popup-Menü ist´s eigentlich egal, deshalb habe ich das "&" entfernt (wie man in der Funktion ja sehen kann) und zeige den Text einfach so an.

        Und eine weitere Ergänzung wäre, dass man die Farbbitmap in eine Graustufen-Bitmap konvertiert, weil "Glyph.Monochrome:=true" zwar schön leicht ist, aber sch... aussah. )

        Kann doch noch jemand weiterhelfen?
        Gruß, Mathias

        Comment


        • #5
          Hi<br>

          DrawText(ACanvas.Handle, PChar(S), Length(S), Rect, dt_SingleLine or dt_Left or dt_VCenter or dt_EndEllipsis);<br>

          zeichnet das unterstrichene Prefix

          Comment


          • #6
            Punkt #1, abgeha(g)kt. )
            Danke, Hagen

            Comment


            • #7
              Also, ich habe auch das Problem mit den schwarz/weißen Bitmaps gelöst.
              Na ja, es ist nur getrickst. Da nicht vorgesehen ist, diese Bitmaps zur Laufzeit zu ändern, habe ich per Grafikprogramm einfach selbst welche erstellt und an die ImageListe angehangen. Im Code steht dann nur noch:

              <PRE><CODE>
              if((odDisabled in State) or (odGrayed in State)) and (xMI.ImageIndex + 3 <= PopupImg.Count) then PopupImg.GetBitmap(xMI.ImageIndex + 3,Glyph)
              else PopupImg.GetBitmap(xMI.ImageIndex,Glyph);
              </CODE></PRE>

              Und es kann einiges rausfliegen.
              Zum einen beim selektierten Eintrag: wenn man zunächst das Rechteck füllt und dann erst den Rahmen zeichnet, spart man sich die Anweisungen zum Verkleinern (dec, inc). Man muss hinterher nur wieder "ACanvas.Brush.Color := pop_bg_selected;" setzen, sonst wird mit der Rahmenfarbe weitergemalt.

              Und weil das Rechteck also nicht kleiner wird, kann man bei den Bitmaps (sowohl den echten als auch dem Check-Haken) die "if"-Zeile komplett rausnehmen, so dass nur noch der "else"-Teil übrig bleibt (ohne else natürlich).

              Dann hat mein Compiler noch gemeckert, weil er mit einer Anweisung von "DrawText" nichts anfangen konnte. Aber da fehlte bloß ein Unterstrich (das passiert mir dauernd). Hagens korrekter Wert sollte also "dt_End_Ellipsis" heißen.

              Da ich in meinem Programm (PS: Hagen, das ist das Ding, weswegen ich dich und Andreas immer mit den Mailslots genervt habe ) auch dynamische Menüeinträge habe, die zur Laufzeit geändert werden können, gibt es auch noch die Anweisungen zum Erzeugen dieser zusätzlichen Menüpunkte:

              <PRE><CODE>
              ...
              mii := TMenuItem.Create(PhraseMenuItem);
              ...
              mii.OnAdvancedDrawItem := OnDrawPhrasesNow;
              ...
              </CODE></PRE>

              Hier habe ich einfach "OnAdvancedDrawItem" auf eine neue, interne Funktion gelinkt, die nichts weiter macht, als ihrerseits dann die obige "CustomPopupDraw" aufzurufen.

              Nebenbei: Es ist kein Bug, im rechten Fenster fehlen tatsächlich die Pics in der Listbox, weil´s sozusagen der Classic-"Skin" ist, und die alte Version hatte nun mal keine Grafiken. Und sie ändert gleich noch das Aussehen des Kontextmenüs mit.

              Was jetzt noch fehlt, wären personalisierte Menüs (bzw. Listeneinträge), aber Gesine sagt: "Personalisierte Menüs sind doof." Also, lass ich´s. )

              Das hat ´ne Menge Spaß gemacht. Aber eine Frage hätte ich trotzdem noch: gibt es irgendein richtig gutes Buch (oder ´ne andere Info-Quelle), in dem man erfahren kann, wie man die diversen Elemente von Windows quasi selbst gestalten kann. Logo, es gibt Komponenten (und diese FlatStyle-Teile sehen z.B. wirklich gut aus), aber man lernt nichts dabei.

              Gruß.
              Mathias

              Comment


              • #8
                Ich weiß, es nervt ), aber hat jemand noch eine Idee, wie man den 3D-Look des Menüs beeinflussen kann? Ich hab´s zwar hingekriegt, allerdings <u>muss</u> dazu leider das Menü erst mal mit der Maus erobert werden:

                <pre>
                if(flat_popup = 1) then
                &nbsp;&nbsp;begin
                &nbsp;&nbsp;&nbsp;&nbsp;hDcM := ACanvas.Handle; hWndM := WindowFromDC(hDcM); if(hWndM <> PopEyh5.Handle) then
                &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;begin
                &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Dr awMenuBorder(hWndM);
                &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;end;
                &nbsp;&nbsp;end;
                </pre>

                Und die entsprechende Funktion "DrawMenuBorder" enthält im Prinzip nur den Zugriff auf das DC, und dann wird über die "Rectangle"-Funktion einfach ein Rahmen um das Menü gezogen, und der 3D-Stil wird auch zweimal mit der Hintergrundfarbe überzeichnet, damit das Menü einen flachen Look bekommt.

                Aber wie gesagt: leider erst, nachdem man mit dem Mauszeiger in das Menü reingeht. Wo muss ich ansetzen, um das gleich hinzubekommen? Ich hab´s schon bei "onPopup" probiert, aber das ist nichts passiert.

                Hoffnungsvoll wartend,
                Mathias

                Comment


                • #9
                  Och, hat denn keiner einer Idee?
                  (

                  Ich seh´ nicht mehr durch. Ich hab´s mit "WM_INITMENUPOPUP" probiert und mit "WM_CONTEXTMENU" (und mit diversen Paint-Nachrichten), aber entweder habe ich etwas falsch gemacht (weil das Programm nicht auf diese Nachrichten reagiert), oder mein Ansatz ist generell falsch.

                  Ich will doch nur den 3D-Rahmen des Menüs im Vorfeld ersetzen, also wenn es angezeigt/erstellt/gezeichnet wird, damit es gleich den "flachen" Look hat und nicht erst dann, wenn ich mit der Maus drübergehe

                  Comment


                  • #10
                    Hi Mathias

                    Tja, Du hast Dir eine sehr sehr harte Nuss vorgenommen. Wenn Du WM_INITMENUPOPUP hast und abfangen kannst dann wäre das der ideale Zeitpunkt, das Fensterhandle des Popups zu "hooken" , bzw. "subclass'en". Damit kommst Du dann an die Messages WM_NCPaint und WM_Paint ran. Die Dich interessieren.

                    gruß Hage

                    Comment


                    • #11
                      Na ja, ich hab´s versucht, aber das Problem war/ist, dass er nicht auf die Message reagiert hat. Ich hab´ mal in den Source von Borlands "menus.pas" reingesehen -- liegt das daran, dass dort intern "TPopupList" definiert ist, die diese Message bereits nutzt? Oder ist´s mein Fehler.

                      Also, ich hab´s versucht mit:
                      <pre>
                      procedure TForm1.WMInitMenuPopup(var Message: TMessage);
                      begin
                      &nbsp;&nbsp;if(Message.Msg = WM_INITMENUPOPUP) then
                      &nbsp;&nbsp;&nbsp;&nbsp;begin
                      &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// bla
                      &nbsp;&nbsp;&nbsp;&nbsp;end;
                      &nbsp;&nbsp;inherited;
                      end;
                      </pre>

                      Problem 1: Ich bin nicht sicher, ob in dem Fall das "inherited" nicht besser <b>vorher</b> kommen sollte. Problem 2: Ich wollte eigentlich als Reaktion (um zu sehen, ob überhaupt was passiert), meine Caption ändern. Aber nix war.
                      Und ja: Ich habe im "private"-Teil diese Prozedur entsprechend mit der "message WM_INITMENUPOPUP" deklariert.

                      &lt;treu guck&gt; Äh, Hagen, du hast nicht zufällig irgendwo ein Stückchen Source rumliegen? Keine ganze Komponente, nur dieses ... kleine ... &lt;Augenblinker&gt; Oder steht´s in irgendeinem Buch? &lt;zu Andreas guck&gt; Ich finde überall nur, wie man Menüeinträge benutzerdefiniert zeichnet, aber das hab´ ich mittlerweile ja gerafft (mehr oder weniger ;o). Aber zum Zeichnen von ganzen Menüs schweigt sich das Web aus.

                      Trotzdem danke für die Antwort, ich dachte schon, mit mir redet keiner mehr. )

                      Gruß,
                      Mathias

                      Comment


                      • #12
                        Hi

                        Richtig, weil eben das Windows-Fenster der PopupMenus besonderst geschützt ist und es KEINE offiziellen Wege gibt überhaupt auf dieses Fenster-Handle zuzugreifen. Der Weg mit WindowFromDC() ist ein solcher wenig bekannter Weg, der zudem im alten Win3.1 noch garnicht existierte. Ein anderer Weg ist mit FindWindow() im richtigen Moment das erste PopupMenu zu suchen. Dies KANN nur das oberste PopupMenu sein, da das Windows-System eigentlich immer nur EIN PopupMenu zur gleichen Zeit unterstützen kann.
                        Dein Problem mit der WM_INITMENUPOPUP Messages ist einfach erklärt. Schau mal in Menu.pas bei der Methode .Popup() nach. Dort wirst Du sehen das TrackPopupMenu(ex)() aufgrufen wird, und in dieser API Funktion wird das Fensterhandle das alle wichtigen Menu-Messages empfangen soll angegeben. Im Falle von Delphi PopupMenus ist das ein internes Object, benannt TPopupList. Um nun die Messages an ein eigenes fenster zu senden darf also entweder NICHT TPopupMenu.Popup() verwendet werden, sondern stattdessen TrackPopupMenu() ODER die Eigenschaft TPopupMenu.WindowHandle sollte verändert werden. Leider wird das durch Delphi NICHT so richtig unterstützt. Nun, das OS erstellt ein Fenster das als Darstellungs-Kontainer des PopupMenus fungiert. Beim Sichtbarmachen sendete es dann z.B. einen Message wm_InitMenuPopup an das mit TrackPopupMenu(9 "verlinkte" Fensterhandle. Dabei ist es zu keinem Zeitpunkt so richtig möglich das eigentliche Fensterhandle des PopupMenu Kontainerfenster's zu bekommen. Es gibt einfach keine API Funktionen die von einem Menuhandle dieses Fensterhandle zurückgeben.

                        Trotzdem gibt es Wege: Ich preferiere und nutzte meistens folgenden Weg. Da jedes PopupMenu-Kontainerfenster Deinem Prozess zugeordnet ist kann man mit einem Prozesslokalen Hook arbeiten. Da die wichtigen Messages mit SendMessage() verschickt werden bietet sich SetWindowsHookEx(wh_CallWndProc, MyMenuHook,..) an. Nun empfängt die MyMenuHook alle solche Messages wie wm_Create, wm_NCCreate usw. Im besonderen wm_Create ist interessant, da hier der Klassenname des Fenster ausgewertet werden kann. Ist dieser #32768 dann IST es ein PopupMenu Fensterhandle. Nun kann direkt im wm_Create die übergebene Standardfensterprocedure mit einer eigenen ausgetauscht werden (z.B. mit MyMenuWndProc). Den ganzen Vorgang könnte man mit "dynamisches SubClassing" beschreiben.
                        Die alte=originale Fensterprocedure muss natürlich beim Fensterhandle gespeichert sein. Dazu bieten sich die Funktionen GlobalAddAtom(), GetProp(), RemoveProp() und SetProp() an. Diese ermöglichen die Speicherung zusätzlicher Daten zu einem/an einem Fensterhandle.
                        Deine MyMenuWndProc IST nun die Standard Fensterfunktion ALLE TPopupMenus in DEINEM Prozess und hat zugriff auf ALLE betreffenden Messages, wie wm_NCPaint, wm_EraseBkGnd, wm_Paint, wm_WindowPosChanged.

                        Gruß Hage

                        Comment


                        • #13
                          Na ja, da hab´ ich am Wochenende ja was zu tun.
                          /
                          Aber ich versuch´s selbst. Man wächst mit seinen Aufgaben.
                          Hagen, danke für den Lösungsansatz.

                          Mathias.

                          PS: Ach ja, ich habe deine Komponente (der Link in der Titelleiste) mit der Eigenschaft "Enabled" zum Ein- und Ausschalten erweitert. Jetzt verkauf´ ich´s als meine Idee. ;o) Nee, nur Spaß. Die Komponente gehört zwar zu meiner Sammlung, heißt aber "TReddmann".

                          Comment


                          • #14
                            Jo, gute Idee. ich mag's halt nicht doppelt gemoppelt, und mir reichte einfach der Trick den Text auf ''=leersting zu setzen. das spart also so'ne Eigenschaft. Aber recht haste, heutzutage mus alles "mundgerecht" sein War übrigens eh nur so'n Entspannungsprojekt für 8 Stunden.

                            Gruß hage

                            Comment


                            • #15
                              Bäh, "Entspannungsprojekt" ... Mein momentaner "Ich-lerne-Hooks"-Status sieht so aus: <b>(</b> ... und du redest von <b>Entspannung</b>. Wobei, das mit dem Hook habe ich ja, denke ich. Ich habe auch die Message, auf die´s ankommt.
                              <pre>
                              if(pMsg(lParam)^.Message = wm_create) then ...
                              </pre>

                              Aber wie komme ich jetzt an diesen ver... Klassennamen ran?
                              &lt;seufz&gt; Ich werde mich künftig auf JavaScript beschränken. )

                              Gruß,
                              Mathias.

                              Was den CaptionLink angeht: ich hab´s für den Aufruf einer Hilfedatei genommen; und wenn die Hilfedatei nicht gefunden werden kann, dann soll auch der Link gar nicht existieren. Da ich aber meinen eigenen Cursor verwende, sah´s merkwürdig aus, wenn sich eben dieser veränderte obwohl nichts zu sehen war.

                              Comment

                              Working...
                              X