Crash pluginu dla TotalCmd po otwarciu wielu modułów gdy ListBox ma styl LBS_OWNERDRAW.

0

Cześć.

Otóż mój plugin .wlx dla Total Commandera po wielu testach działa i przepisany w pełni dla WinAPI. Jednak zanim puszczę jego wersję 0.4 na totalcmd.net to chcę spróbować dopracować jedną rzecz. Mianowicie w swoim pluginie do wyświetlania sampli używam ListBox'a, który tworzę tak:

  SamplesLBHandle := CreateWindowEx(WS_EX_CLIENTEDGE, 'ListBox', '',
    WS_CHILDWINDOW or WS_VISIBLE or WS_BORDER or WS_VSCROLL or LBS_HASSTRINGS,
    SamplesLB_Left, SamplesLB_Top, SamplesLB_Width, SamplesLB_Height,
    MainWindowHandle, IDC_SAMPLESLB, HInstance, nil);

Sample wyświetlam tak:

//...
    Channel : DWORD;
    ChannelInfo : BASS_CHANNELINFO;

procedure TPluginObj.ShowSamplesOrInstrumentsData(GBIndex : Byte);
const
  Kinds_Arr_Prefix : array[0..1] of string = ('S', 'I');
  Kinds_Arr : array[0..1] of DWORD = (BASS_TAG_MUSIC_SAMPLE, BASS_TAG_MUSIC_INST);
var
  SmpNum : WORD;
  SmpName : PChar;
begin
  SmpNum := 0;
  LBClear(SamplesLBHandle);
  if GBIndex in [0..1] then
  begin
    SmpName := PChar(BASS_ChannelGetTags(Channel, Kinds_Arr[GBIndex] + SmpNum));
    while SmpName <> nil do
    begin
      LBAddString(SamplesLBHandle, FormatC(Sample_Names_Format, Kinds_Arr_Prefix[GBIndex], SmpNum + 1, SmpName));
      SmpNum := SmpNum + 1;
      SmpName := PChar(BASS_ChannelGetTags(Channel, Kinds_Arr[GBIndex] + SmpNum));
    end;
  end;
end;

Przy ładowaniu kolejnych modułów nie tworzone jest nowe okno, zgodnie z prawidłami użycia eksportowanej przez plugin funkcji ListLoadNext i ListLoadNextW. Dodatkowo poprzedni moduł jest zwalniany przez BASS_MusicFree(Channel). I teraz kiedy tworzę ListBox bez stylu LBS_OWNERDRAWFIXED to wszystko jest ok, mogę odtwarzać w szybkim podglądzie wiele modułów wiele razy i nic się nie zcrashuje.

Crash objawia się tym, że plugin traci tło pod częścią kontrolek, znikają obrazki na staticach i po zamknięciu podglądu i próbie otwarcia kolejnego modułu muzycznego nie widać pluginu w oknie tylko słychać odtwarzany moduł, a czasem i następuje w ogóle zamknięcie samego Total Commandera.

A zależy mi na własnym rysowaniu ListBoxa z jednego prostego względu, chciałbym uzyskać przezroczysty pasek zaznaczenia. Chyba, że da się to osiągnąć inaczej niż przez samodzielne rysowanie. Rysowanie odbywa się poprzez komunikatu WM_DRAWITEM w funkcji obsługi okna głownego. Czyli wygląda to tak:

procedure TPluginObj.OnDrawSamplesLB(DIS : PDrawItemStruct);
var
  S : string;
begin
  S := LBGetItemText(DIS.hwndItem, DIS.itemID);
  if (DIS.itemAction = ODA_FOCUS) and (DIS.itemState and ODS_FOCUS > 0) then
  begin
    SetTextColor(DIS.hDC, ColorToRGB(ForeColor));
    SetBkColor(DIS.hDC, ColorToRGB(BackColor));
    FillRect(DIS.hDC, DIS.rcItem, CreateSolidBrush(ColorToRGB(BackColor)));
  end
  else
  begin
    SetTextColor(DIS.hDC, ColorToRGB(ForeColor));
    SetBkMode(DIS.hDC, TRANSPARENT);
    FillRect(DIS.hDC, DIS.rcItem, CreateSolidBrush(ColorToRGB(BackColor)));
  end;
  DrawTextEx(DIS.hDC, PChar(S), Length(S), DIS.rcItem, DT_LEFT, nil);
  if DIS.itemState and ODS_FOCUS > 0 then
  begin
    DrawFocusRect(DIS.hDC, DIS.rcItem);
  end;
end;

procedure TPluginObj.MainWndProc(var AMessage : TMessage);
var
  DC : HDC;
  R : TRect;
  HB : HBRUSH;
  DefaultWidth : integer;
  WinPos : ^TagWindowPos;
  DrawItem : PDrawItemStruct;
  PaintStruct : TPaintStruct;
  MeasureItem : PMEasureItemStruct;
begin
  with AMessage do
  begin
    Result := 0;
    case Msg of
      WM_DRAWITEM :
        begin
          DrawItem := Pointer(LParam);
          OnDrawSamplesLB(DrawItem);
        end;
      WM_MEASUREITEM :
        begin
          MeasureItem := Pointer(LParam);
          MeasureItem.itemHeight := SamplesLB_ItemHeight;
        end;
//...

Wszystkie funkcje obsługi okna są przypisane przez MakeObjectInstance, którego kod "wyprułem" sobie ze źródel VCL dla Delphi 7 Enterprise. I dla otwarcia killku plików jest ok, ale próba otwarcia (oczywiście poprzedni jest zwalniany) po sobie kilkudziesięciu lub kilkuset to crash. A docelowo do tego plugin ma służyć. Prosił bym o poradę, jak można usprawnić rysowanie. Moze coś robię nie tak.

Dodam, że gdy początkowo plugin był napisany pod VCL, robiły się identyczne "cyrki". Pokazywał mi się wyjątki. Kodu VCL już nie mam. Ale tam robiłem rysowanie w taki sposób:

procedure TModulesPlayerForm.SamplesLBDrawItem(Control : TWinControl; Index : Integer;
  Rect : TRect; State : TOwnerDrawState);
var
  MyBrush : TBrush;
begin
  MyBrush := TBrush.Create;
  with (Control as TListBox).Canvas do
  begin
    MyBrush.Style := bsSolid;
    MyBrush.Color := SamplesLB.Color;
    (Control as TListBox).Canvas.Font.Color := SamplesLB.Font.Color;
    Windows.FillRect(handle, Rect, MyBrush.Handle);
    Brush.Style := bsClear;
    TextOut(Rect.Left, Rect.Top, (Control as TListBox).Items[Index]);
    MyBrush.Free;
  end;
end;

I zdaje się, że powodowalo to wyjątek znajdujący się w kodzie VCL modułu graphics.pas poniżej. Jednak kiedy dodawałem sprawdzenie czy Canvas.Handle > 0 to nic nie dawało. U siebie w kodzie też dla pewności dodałem sprawdzanie czy DC pobrane z WParam jest różne od zera. Oczywiście bez powodzenia.

procedure TCanvas.RequiredState(ReqState: TCanvasState);
var
  NeededState: TCanvasState;
begin
  NeededState := ReqState - State;
  if NeededState <> [] then
  begin
    if csHandleValid in NeededState then
    begin
      CreateHandle;
      if FHandle = 0 then
        raise EInvalidOperation.CreateRes(@SNoCanvasHandle);
    end;
    if csFontValid in NeededState then CreateFont;
    if csPenValid in NeededState then CreatePen;
    if csBrushValid in NeededState then CreateBrush;
    State := State + NeededState;
  end;
end;

Prosil bym o doradzenie mi. Jeżeli z Waszymi radami nie uda się ogarnąć problemu, to trudno. Będę musiał pogodzić się z ListBox'em, ktorego kolor zaznaczenia psuje efekt wizualny. Dodam, że kolory dla tła oraz dla fontu tekstów można zmienić z domyślnych clBlack i clLime na takie, jakie user ma ustawione (jeśli śą) w preferencjach dla Listera. Jednak wtedy efekt, ładnego wyglądu psuje właśnie wypełniona ramka zaznaczenia ListBoxa.

1
    SetTextColor(DIS.hDC, ColorToRGB(ForeColor));
    SetBkColor(DIS.hDC, ColorToRGB(BackColor));

Grzebiesz w hDC, a nie przywracasz stanu pierwotnego. Uchwyty DC migrują między oknami (z wyjątkiem okien które mają styl CS_OWNDC), stąd mogą być te artefakty w innych oknach – bo im po prostu mieszasz.

Pobierz najpierw pierwotny kolor i przywróć go po narysowaniu.

FillRect(DIS.hDC, DIS.rcItem, CreateSolidBrush(ColorToRGB(BackColor)));

Tu masz wyciek uchwytu pędzla. Po CreateBrush trzeba zwolnić pędzel przez DeleteObject.

0

Dziękuję za odpowiedź. Zrobiłem tak chyba jak powinno być, bo podpatrzyłem przyklad kodu na stronie: http://www.angelfire.com/hi5/delphizeus/brushpen.html (po słowach WM_PAINT: begin) i teraz mam tak:

procedure TPluginObj.OnDrawSamplesLB(DIS : PDrawItemStruct);
var
  S : string;
  Font1 : HFONT;
  Brush1, HB : HBRUSH;
  OrigFont, OrigBrush : THandle;
begin
  Brush1 := CreateSolidBrush(ColorToRGB(BackColor));
  Font1 := CreateFont(Round(-12 * 111 / 72), 0, 0, 0, FW_NORMAL, 0, 0, 0,
    OEM_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,
    DEFAULT_QUALITY, DEFAULT_PITCH, 'Terminal');
  OrigFont := SelectObject(DIS.hDC, Font1);
  OrigBrush := SelectObject(DIS.hDC, Brush1);
  S := LBGetItemText(DIS.hwndItem, DIS.itemID);
  if (DIS.itemAction = ODA_FOCUS) and (DIS.itemState and ODS_FOCUS > 0) then
  begin
    SetTextColor(DIS.hDC, ColorToRGB(ForeColor));
    SetBkColor(DIS.hDC, ColorToRGB(BackColor));
    HB := CreateSolidBrush(ColorToRGB(BackColor));
  end
  else
  begin
    SetTextColor(DIS.hDC, ColorToRGB(ForeColor));
    SetBkMode(DIS.hDC, TRANSPARENT);
    HB := CreateSolidBrush(ColorToRGB(BackColor));
  end;
  FillRect(DIS.hDC, DIS.rcItem, HB);
  DeleteObject(HB);
  DrawTextEx(DIS.hDC, PChar(S), Length(S), DIS.rcItem, DT_LEFT, nil);
  if DIS.itemState and ODS_FOCUS > 0 then
  begin
    DrawFocusRect(DIS.hDC, DIS.rcItem);
  end;
  SelectObject(DIS.hDC, OrigBrush);
  SelectObject(DIS.hDC, OrigFont);
end;

A sposób obslugi komunikatu (wycinek) jest poniżej:

procedure TPluginObj.MainWndProc(var AMessage : TMessage);
var
  DC : HDC;
  R : TRect;
  HB : HBRUSH;
  DefaultWidth : integer;
  WinPos : ^TagWindowPos;
  DrawItem : PDrawItemStruct;
  PaintStruct : TPaintStruct;
  MeasureItem : PMEasureItemStruct;
begin
  with AMessage do
  begin
    Result := 0;
    case Msg of
      WM_DRAWITEM :
        begin
          DrawItem := Pointer(LParam);
          case LoWord(WParam) of
            IDC_SAMPLESLB :
              begin
                OnDrawSamplesLB(DrawItem);
                Result := 1;
                Exit;
              end;
          end;
        end;
      WM_MEASUREITEM :
        begin
          MeasureItem := Pointer(LParam);
          MeasureItem.itemHeight := SamplesLB_ItemHeight;
        end;
//...

Jednak zoowu zrobił się crash kolorów i pluginu, po większej ilości plików. Być może problemem jest jeszcze rysowanie grafiki na staticach:

//...

procedure DrawTransparentBitmap(DC : HDC; HBmp : HBITMAP; XStart, YStart : integer; TransparentColor : COLORREF);
var
  BM : BITMAP;
  PtSize : TPOINT;
  CColor : COLORREF;
  HdcMem, HdcBack, HdcObject, HdcTemp, HdcSave : HDC;
  BMAndBack, BMAndObject, BMAndMem, BMSave, BMBackOld, BMObjectOld, BMMemOld, BMSaveOld : HBITMAP;
begin
  HdcTemp := CreateCompatibleDC(DC);
  SelectObject(HdcTemp, HBmp);
  GetObject(HBmp, SizeOf(BITMAP), @BM);
  PtSize.X := BM.bmWidth;
  PtSize.Y := BM.bmHeight;
  DPToLP(HdcTemp, PtSize, 1);
  HdcBack := CreateCompatibleDC(DC);
  HdcObject := CreateCompatibleDC(DC);
  HdcMem := CreateCompatibleDC(DC);
  HdcSave := CreateCompatibleDC(DC);
  BMAndBack := CreateBitmap(PtSize.X, PtSize.Y, 1, 1, nil);
  BMAndObject := CreateBitmap(PtSize.X, PtSize.Y, 1, 1, nil);
  BMAndMem := CreateCompatibleBitmap(DC, PtSize.X, PtSize.Y);
  BMSave := CreateCompatibleBitmap(DC, PtSize.X, PtSize.Y);
  BMBackOld := SelectObject(HdcBack, BMAndBack);
  BMObjectOld := SelectObject(HdcObject, BMAndObject);
  BMMemOld := SelectObject(HdcMem, BMAndMem);
  BMSaveOld := SelectObject(HdcSave, BMSave);
  SetMapMode(HdcTemp, GetMapMode(DC));
  BitBlt(HdcSave, 0, 0, PtSize.X, PtSize.Y, HdcTemp, 0, 0, SRCCOPY);
  CColor := SetBkColor(HdcTemp, TransparentColor);
  BitBlt(HdcObject, 0, 0, PtSize.X, PtSize.Y, HdcTemp, 0, 0, SRCCOPY);
  SetBkColor(HdcTemp, CColor);
  BitBlt(HdcBack, 0, 0, PtSize.X, PtSize.Y, HdcObject, 0, 0, NOTSRCCOPY);
  BitBlt(HdcMem, 0, 0, PtSize.X, PtSize.Y, DC, XStart, YStart, SRCCOPY);
  BitBlt(HdcMem, 0, 0, PtSize.X, PtSize.Y, HdcObject, 0, 0, SRCAND);
  BitBlt(HdcTemp, 0, 0, PtSize.X, PtSize.Y, HdcBack, 0, 0, SRCAND);
  BitBlt(HdcMem, 0, 0, PtSize.x, PtSize.y, HdcTemp, 0, 0, SRCPAINT);
  BitBlt(DC, XStart, YStart, PtSize.X, PtSize.Y, HdcMem, 0, 0, SRCCOPY);
  BitBlt(HdcTemp, 0, 0, PtSize.X, PtSize.Y, HdcSave, 0, 0, SRCCOPY);
  DeleteObject(SelectObject(HdcBack, BMBackOld));
  DeleteObject(SelectObject(HdcObject, BMObjectOld));
  DeleteObject(SelectObject(HdcMem, bMMemOld));
  DeleteObject(SelectObject(HdcSave, BMSaveOld));
  DeleteDC(HdcMem);
  DeleteDC(HdcBack);
  DeleteDC(HdcObject);
  DeleteDC(HdcSave);
  DeleteDC(HdcTemp);
end;

procedure TPluginObj.OnDrawStatics(DIS : PDrawItemStruct);
var
  IcoH : HICON;
  IconInfo : TIconInfo;
begin
  IcoH := LoadImage(HInstance, MAKEINTRESOURCE(GFX_BASE + (GetDlgCtrlId(DIS.hwndItem) mod 100)), IMAGE_ICON, 32, 32, LR_DEFAULTCOLOR);
  GetIconInfo(IcoH, IconInfo);
  DrawTransparentBitmap(DIS.hDC, IconInfo.hbmColor, 0, 0, RGB(0, 0, 0));
end;

Które obslugiwane jest tak:

      WM_DRAWITEM :
        begin
          DrawItem := Pointer(LParam);
          case LoWord(WParam) of
            IDC_PLAYBTN, IDC_PAUSEBTN, IDC_STOPBTN :
              begin
                OnDrawStatics(DrawItem);
              end;
          end;
        end;
//...

Początkowo grafikę ustawiałem na przez STM_SERIMAGE, ale sposób podmieniania kolorów pokazany w innym wątku, niestety źle działa pod Winodes 98. A w założeniu plugin powinien działać i na tym systemie. Co testowałem na szybko pod VMWare Workstation 8. A przyciski są trzy osobne (Play/Pause/Stop) i tworzone tak jak poniżej tylko w róznych koordynatach na GroupBoxie - rodzicu:

  PlayBtnHandle := CreateWindow('Static', '',
    SS_OWNERDRAW or WS_CHILD or WS_VISIBLE or SS_NOTIFY,
    GetControlLeft(ModulePositionSBHandle),
    GetControlTop(ModulePositionSBHandle) + GetControlHeight(ModulePositionSBHandle) + Controls_Vert_Distance,
    Buttons_Icon_Size, Buttons_Icon_Size, ControlsGBHandle, IDC_PLAYBTN, HInstance, nil);

Nie wiem co tutaj poprawić dla staticów, o ile tak może być, ponieważ samo DrawTransparentBitmap zwalnia co potrzeba (chyba). Prosił bym Ciebie @Azarien albo ktoś inny jak wie, o przykładowy kod. Jak to poprawić. Ponieważ teraz plugin, wywala się po otwarciu większej ilości plików, ale nadal jednak wywala. Co może być przypadkiem, bo jego wykrzaczenie nieraz następuje po kilkuset plikach, a nieraz po otwarciu w podglądzie Ctrl+Q Total Commandera około setki.

EDIT: bez rysowania staticów, ale z ListBoxem tak jak powyżej pokazałem w kodzie na samym początku tego posta, nadal się wywala. Pewnie robię coś nie tak. Prosił bym o przykład z poprawieniem kodu, bo chyba źle interpretuje to co pokazano na tej stronie na AngelFire.

0

Ech narobiłeś tych zmiennych że trudno to przeanalizować. Zapamiętać i przywracać domyślny stan masz tylko DC które dostajesz w DRAWITEMSTRUCT przy komunikacie WM_DRAWITEM tymi utworzonymi przez CreateCompatibleDC się nie przejmuj je tylko musisz usunąć DeleteDC tak samo jak te Birmapy aż 4 nie da się tego jakoś zoptymalizować? DeleteObject usuń normalnie uchwyty stworzonych Bitmap nie kombinuj już przesadnie z tym przywracaniem SelectObject niepotrzebnych rzeczy (po co przywracać do poprzedniego stanu to co usuwasz?) .
A z tym LoadImage za każdym razem to nie przesadzasz? Ile razy można ja wiem że system to zwalnia ale przy kończeniu się procesu więc ile można?

0

@kAzek: kod rysowania przezroczystej bitmapy, wygooglowałem kiedyś i ponieważ działa, nie wdawałem się w zmienianie go za bardzo, ponieważ brak mi doświadczenia z obsługą GDI jak widzicie. Nie jestem perfekcyjnym programistom, raczej samoukiem. Plugin napisany jak umiem. Pozostają tylko te problemy z rysowaniem. Po ich ogarnięciu plugin byłby gotowy za pewne do wydania na świat.

@Azarien: na prywatną wiadomośc wysłalem Wam link do kodu źródłowego i dużej paczki modułów, aby było na czym testować. Dodatkowe informacje rownież w tejże wiadomości. Prosił bym póki co nie publikować kodu źródłowego. Nie jest on szczegółną tajemnicą. Planuje go i tak po dopracowaniu i przetestowaniu przy wydaniu tej wersji pluginu opublikowac poza binarką na totalcmd.net - a kiedy uda się Wam dopracować działający kod, to podzielę się prawidłowym fragmentem kodu tutaj.

Problem z tym, że na mnóstwie stron do wygooglowania, z ktorych czepałem przykłady (tłumacząc często z C++, poza kodami z tej strony na angelfire) kody ilustrujące WM_DRAWITEM nie są właśnie tak dopracowane, aby obsłużone było wszystko jak każą na MSDNie.

1 użytkowników online, w tym zalogowanych: 0, gości: 1