2012-03-23

ListBox - zachowanie w MultiExtended

Mówimy tutaj o ListBox z WinForms w trybie zaznaczania MultiExtended.

Zaznaczmy jakiś element. Czyścimy listę, wypełniamy nowymi wartościami. Klikamy z shiftem w inny element i otrzymujemy zaznaczenie od "jednego bliżej" w stosunku do poprzednio klikniętego elementu do elementu klikniętego teraz.

Najpierw myślałem, że to jakiś bug, ale tak samo zachowuje się ListBox w MFC, czyli jest to raczej zachowanie Windowsa. Czystej kontrolki ListBox z API nie sprawdzałem - niech wspomnienia zostaną wspomnieniami. Tak więc nie jest to zachowanie WinForms.

Najdziwniejsze jest to, że chyba nic z tym nie da się zrobić. Informacja o poprzednim zaznaczeniu nie jest w żaden sposób dostępna i modyfikowalna. Z drugiej strony jakoś ten ListBox musi się zachować jak ktoś znienacka w niego kliknie z shiftem z zamiarem zaznaczenia. Dla mnie bardziej logiczne by było zaznaczanie tylko aktualnie wybranego elementu.

2012-03-21

Naturalny porządek sortowania

Przez tytułowe hasło możemy rozumieć naprawdę wiele rzeczy. Ale problem głównie związany jest prawidłowym posortowaniem tekstu zawierającego liczby. Inaczej w naszym tekście ciągi liczb chcielibyśmy zastąpić jakby pojedynczym symbolem o wartości liczby. I tutaj możemy się zastanawiać jakie liczby jak wyodrębniać.

Głównym zadaniem sortowania jest radzenie sobie z prawidłowym sortowaniem string-ów z jakąś numeracją, kiedy te numery nie są uzupełniane zerami na początku (np. pliki).

Alternatywnie możemy skorzystać z gotowej metody, takiej samej jaką używa system do sortowania plików. Jest już napisana, przetestowana, użytkownik nie zostanie specjalnie zaskoczony działaniem sortowania innym od systemowego zwłaszcza jeśli idzie o nazwy plików. I być może przy okazji odpadają nam problemy z lokalizacją, które musielibyśmy uwzględnić pisząc taką metodę sami.

Sam kod jest bardzo prosty:

internal class NaturalOrderStringComparer : IComparer<string>
{
    [DllImport("shlwapi.dll", CharSet = CharSet.Unicode, ExactSpelling = true)]
    private static extern int StrCmpLogicalW(String a_x, String a_y);

    public int Compare(string a_x, string a_y)
    {
        return StrCmpLogicalW(a_x, a_y);
    }
}

Ikona w tray-u, a restart explorer-a

Explorer to dosyć niestabilny proces. Wchodzenie na 100% obciążenia, crash i restart zdarzają się od czasu do czasu. W takim wypadku ikonki naszych programów znikają z tray-a. Znikają ale programy działają dalej. Jeśli nasz program był zminimalizowany do tray-a to nie mamy go jak przywrócić. Można napisać aplikacje tak by uruchomienie drugiej instancji przywróciło pierwszą. Ale co jeśli dopuszczamy działanie wielu kopii aplikacji. Zawsze możemy oczywiście program zamknąć z menadżera zadań. Ale u mnie np. jest tych ikonek mnóstwo i jak się sypnie explorer to ich tak trochę ubywa. Naprawdę nie sposób się połapać co znikło do momentu aż jest to potrzebne.

Tak więc to czego potrzebujemy to automatyczne przywracanie ikony w tray-u po restarcie explorer-a. Korzystamy tutaj z message-a jaki explorer wyśle do nas w momencie utworzenia paska zadań. Wiadomość ta nie ma przypisanego kodu, explorer rejestruje ją za pomocą nazwy TaskbarCreated.

Po pierwsze potrzebujemy eksportu do funkcji rejestrowania wiadomości:

[DllImport("user32.dll", SetLastError=true, CharSet=CharSet.Auto)]
private static extern uint RegisterWindowMessage(string a_name);

Sama rejestracja:

private uint WM_TASKBARCREATED;
WM_TASKBARCREATED = RegisterWindowMessage("TaskbarCreated");

I przechwytywanie wiadomości:

protected override void WndProc(ref Message a_msg)
{
    if (a_msg.Msg == WM_TASKBARCREATED)
    {
        if (notifyIcon.Visible)
            notifyIcon.Visible = true;
    }

    base.WndProc(ref a_msg);
}

2012-03-19

SequentialPartitioner

Poniższy Partitioner zwraca elementy w kolejności w jakiej występują one w źródle enumeracji, niezależnie na ile wątków dane będą dzielone. Inaczej mówiąc enumeratory sub-podziałów zwracają elementy w kolejności jak w źródłowej kolekcji niezależnie od tego jak te elementy z sub-podziałów wyciągamy.

Narzut synchronizacji dla takiej metody jest dosyć duży. Nie jest ona także przyjazna dla pamięci cache. Zastosowanie takiej metody podziału to np. pobieranie plików z sieci:

  • przetwarzanie pojedynczego elementu jest długotrwałe
  • wątek przez większość czasu jest zablokowany
  • chcemy by elementy były pobierane w porządku występowania w kolekcji

GetDynamicPartitions() może być wywołany więcej niż jeden raz w trakcie życia SequentialPartitioner. Stąd w tej metodzie za każdym razem musimy tworzyć nasz nowy współdzielony enumerator. Umieszczenie go w obiekcie było by błędem.

yield return nie umieszczamy w bloku lock gdyż wynik zostanie zwrócony podczas założonej blokady co w praktyce oznacza sekwencyjne przetwarzanie elementów kolekcji.

Wywołanie return GetDynamicPartitions(m_source.GetEnumerator()); jest konieczne. Jeśli enumerator będziemy tworzyć w głównej metodzie to za każdym wywołaniem metody (pobraniem enumeratora dla osobnego wątku) zostanie on utworzony na nowo.

public class SequentialPartitioner<T> : 
    Partitioner<T>
{
    private readonly IList<T> m_source;

    public SequentialPartitioner(IList<T> a_source)
    {
        m_source = a_source;
    }

    public override bool SupportsDynamicPartitions
    {
        get
        {
            return true;
        }
    }

    public override IList<IEnumerator<T>> 
        GetPartitions(int a_partition_count)
    {
        var dp = GetDynamicPartitions();
        return (from i in Enumerable.Range(0, a_partition_count)
                select dp.GetEnumerator()).ToList();
    }

    public override IEnumerable<T> GetDynamicPartitions()
    {
        return GetDynamicPartitions(m_source.GetEnumerator());
    }

    private static IEnumerable<T> 
        GetDynamicPartitions(IEnumerator<T> a_enumerator)
    {
        while (true)
        {
            T el;
            lock (a_enumerator)
            {
                if (a_enumerator.MoveNext())
                    el = a_enumerator.Current;
                else
                    yield break;
            }

            yield return el;
        }
    }
}