2012-03-26

Winforms - bugi

Tutaj dobre źródło informacji o błędzie: http://social.msdn.microsoft.com/Forums/ar/winformsdesigner/thread/6f56b963-df4d-4f26-8dc3-0244d129f07c

Post jest z 2006 roku. Mamy 2012 i błąd ciągle jest. Jestem prawie pewien, że został zgłoszony nie raz.

W skrócie chodzi o to, że jeśli umieścimy sobie ToolStrip na TabPage to ten ToolStrip sobie potrafi losowo zniknąć. U mnie najczęściej kiedy kliknę w jakiś przycisk i przeskakuje do kodu.

Rozwiązanie tymczasowe to włączenie wszystkich ToolStrip-ów na starcie aplikacji. Inaczej ryzykujemy z dużym prawdopodobieństwem wypuszczenie aplikacji z pewnymi brakami.

I nie jest to jedyny antyczny bug. Z pamięci ostatnio napotkane mam jeszcze dwa.

Pierwszy związany z serializacją do XML. Jeśli nasza klasa A implementuje interfejs IEnumerable<B>, zaś B implementuje interfejs IEnumerable<C>, zaś C to zwykły obiekt to serializacja obiektu A spowoduje błąd. Generator kodu serializującego zagnieżdża kod w kodzie, w tym wypadku w kodzie serializacji klasy A zagnieżdżany jest kod serializacji klasy B, tylko, że nikt nie wpadł na pomysł zmiany nazw lokalnych zmiennych. I otrzymujemy naprawdę dziwny komunikat o błędzie.

Tutaj jest opis tego błędu: http://connect.microsoft.com/VisualStudio/feedback/details/97762/serializing-types-that-implement-ienumerable-t-causes-error-cs0136. Z roku 2006.

Kolejny to mruganie ListBox-a. Tutaj użyłem jakiegoś kodu z 2008 roku, by to połatać.

Czasami chciałbym żeby Microsoft po prostu połatał swoje produkty, wydał jakąś wersję typu breaking changes i pousuwał bugi, dokonał jakiegoś większego refaktoringu. Np. na szeroką skalę skorzystał z klas generycznych.

2012-04-25:

kolejny bug, kiedy nasz ListBox ma ustawiony IntegralHeight na false i jego wysokość jest taka, że ostatni item wyświetla się w połowie, to jeśli wybierzemy ostatni item do zaznaczenia, scroll ustawi się tak, że będzie on widoczny tylko w połowie.

kolejny bug, prawie nie możliwe jest odtworzenie programowe zaznaczanie, tak by ponowne kliknięcie z shiftem wybrało nowy prawidłowy obszar. Innymi słowy jeśli zaznaczymy od A do B z shiftem, odbudujemy programowo takie zaznaczenie to kiedy naciśniemy na C z shiftem to dostaniemy zaznaczenie od B do C. Pod warunkiem, że indeks C jest większy od indeksy B.

2012-03-25

ToolStripButton.Image, ToolStripMenuItem.Image - skalowanie wysokiej jakości

Jeśli rozmiar ToolStripButton.Image jest większy niż ToolStrip.ImageScalingSize obrazek zostanie przeskalowany, będzie to jednak skalowanie niskiej jakości - najbliższy sąsiad. To samo dotyczy ToolStripMenuItem.Image.

Rozwiązania tego problemu są następujące:
  • napisanie własnego menadżera rysowania (tego nie jestem pewny)
  • przygotowanie obrazków we właściwych rozmiarach
  • ich programowe  przeskalowanie na starcie aplikacji
Pierwszego nie jestem pewny. Drugie jest za mało uniwersalne. Co jeśli zmienimy rozmiar obrazków na toolbarach w trakcie projektowania - musimy wszystkie na nowo przygotować. Co jeśli zmieniamy je programowo - małe, średnie, duże - musimy przygotować trzy zestawy. I ostatni powód - taki sam problem jest z menu kontekstowym, tylko tam obrazki są 16x16.

Ja osobiście zdecydowałem się na trzecie rozwiązanie. Przygotowujemy sobie obrazki jako przeźroczyste PNG o rozmiarach 256x256 i skalujemy je do odpowiednich rozmiarów w razie potrzeby.

Wadą tego rozwiązania jest to, że nasz przeskalowany obrazek - 32x32 mniej, ale 16x16 już bardzo - to prawie pixel art - gdzie zręczny grafik dłubiąc ręcznie w pikselach jest w stanie uzyskać obrazek lepszej jakości niż przez przeskalowanie.

Kod jego implementacji:

private void ResizeContextMenuStripImages()
{
    foreach (var c in FindAll<Control>())
    {
        if (c.ContextMenuStrip == null)
            continue;

        foreach (var item in 
            c.ContextMenuStrip.Items.OfType<ToolStripMenuItem>())
        {
            if (item.Image == null)
                continue;

            var image = ResizeImage(
                item.Image, c.ContextMenuStrip.ImageScalingSize);
            item.Image.Dispose();
            item.Image = image;
        }
    }
}

private void ResizeToolStripImages()
{
    foreach (var toolstrip in FindAll<ToolStrip>())
    {
        foreach (var button in toolstrip.Items.OfType<ToolStripButton>())
        {
            if (button.Image == null)
                continue;

            var image = ResizeImage(button.Image, toolstrip.ImageScalingSize);
            button.Image.Dispose();
            button.Image = image;
        }
    }
}

private Bitmap ResizeImage(Image a_image, Size a_size)
{
    Bitmap bmp = new Bitmap(a_size.Width, a_size.Height, a_image.PixelFormat);

    using (Graphics g = Graphics.FromImage(bmp))
    {
        g.CompositingQuality = CompositingQuality.HighQuality;
        g.InterpolationMode = InterpolationMode.HighQualityBicubic;
        g.SmoothingMode = SmoothingMode.HighQuality;
        g.DrawImage(a_image, 0, 0, bmp.Width, bmp.Height);
    }

    return bmp;
}

private IEnumerable<T> FindAll<T>(Control a_control = null)
{
    List<T> list = new List<T>();

    if (a_control == null)
        a_control = this;

    foreach (var c in a_control.Controls.OfType<T>())
        list.Add(c);

    foreach (var c in a_control.Controls.Cast<Control>())
        list.AddRange(FindAll<T>(c));

    return list;
}