\


 Sunday, 20 September 2009
Pozvánka na podzimní kurzy (OOP, UML, základní a pokročilé návrhové vzory)

Aktualizace  10. 11. 2009-  I veřejný kurz Objektovými principy a návrhovými vzory řízený design a vývoj kvalitních aplikací 2 je zcela obsazen včetně náhradníků. Další kurzy se budou konat na jaře 2009. Jestliže máte předběžný zájem a chcete si rezervovat místo, pište prosím na adresu petra@renestein.net.

Aktualizace 15.10.2009  - veřejný kurz Objektovými principy a návrhovými vzory řízený design a vývoj kvalitních aplikací 1 je zcela obsazen včetně náhradníků. Je možné se již hlásit pouze na kurz Pokročilé návrhové vzory a objektové principy 2.

Rád bych Vás pozval na podzimní kurzy OOP a UML a představil oficiálně InHouse kurz, který postupně vykrystalizoval z požadavků zákazníků (OOP 0 - Objektové programování a UML prakticky - rychlý úvod do světa (nejen) objektového programování).

Osnova InHouse kurzu OOP 0 – Objektové programování a UML prakticky – rychlý úvod do světa (nejen) objektového programování:

Školení má dvě varianty -  pro vývojáře i u konstrukcí a prvků jazyka UML, které jsou považovány za analytické, se dělají časté odbočky do kódu, aby vývojáři pochopili, že UML ani principy OOP nejsou nějaké nesmyslné abstrakce, ale užitečné konstrukce, které sami v programovacích jazycích používají denně.

U varianty pro „čisté“ analytiky jsou digrese do kódu minimalizovány, i když v některých místech stále zdůrazňuji, jaké znalosti z oblasti vývoje aplikací musí analytik mít, aby byl pro projekt užitečný a nevytvářel jen dokumentaci pro dokumentaci, kterou vývojáři nevyužijí a (mnohdy oprávněně) považují za nesmyslnou, drahou a projektu nic nepřinášející.

V kurzu se naučíte modelovat jednoduché i složité aplikace s využitím jazyka UML tak, aby následné kódování nebylo výletem do neznáma s nejistými výsledky, ale dobře čitelnou cestou bez temných a záludných míst vedoucích k selhání projektu.

Kurz je vhodný zvláště pro ty, kteří již nejsou spokojeni s vývojem projektů naivním "hurá" způsobem, kdy bez ohledu na složitost systému nevzniká žádný návrh a ihned se přistupuje ke kódování se všemi špatnými důsledky jako jsou podcenění technické a časové náročnosti implementace nebo vytváření drahých a nespravovatelných systémů.

Kurz je určen pro vývojáře, systémové designery, analytiky a projektové manažery, kteří se chtějí se seznámit se základními principy objektového programování a s modelováním v jazyce UML.

· Požadavky na systém a modelování pomocí případů užití (+ příklady).

· Zrychlená funkční specifikace bez zbytečných formalit – příklady.

· Diagram tříd v UML - vztahy mezi elementy diagramu (asociace. agregace, generalizace, závislost, realizace) – vše vykládáno na konkrétních příkladech z praxe + ukázky nejčastějších chyb, se kterými jsem se setkal. Třída, základní principy OOP, operace, atributy, viditelnost členů třídy. Nenásilný přechod k jednoduchým návrhovým vzorům.

· Příklady složitých diagramů tříd.

· Objektový diagram + příklady.

· Sekvenční diagramy a diagramy interakce.

· Vysvětlení stavových diagramů + výhody aplikací řízených přesně definovanými stavovými automaty.

· Diagram aktivit - modelování složitých business procesů v organizaci.

· Výhody a nevýhody UML - vyzdvižení nejvíce používaných postupů, odhození nepotřebné veteše z jazyka UML.

 

Pokud máte o kurz zájem nebo potřebujete další informace, napište prosím na adresu petra@renestein.net.


Veřejný kurz Objektovými principy a návrhovými vzory řízený design a vývoj kvalitních aplikací 1

Datum konání kurzu:  2. 11. – 4. 11. 2009

Místo konání: Hotel VILLA Praha  Okrajní 1, 100 00, Praha 10

U hotelu VILLA je  možné parkovat, po celý den máme k dispozici wifi připojení.

Na kurzu jsou samozřejmě po celý den teplé a studené nápoje a v ceně kurzu jsou obědy v hotelu.

Podrobné informace o kurzu a možnost přihlásit se na kurz

Program kurzu
Výběr z ohlasů na kurz


Veřejný kurz Pokročilé návrhové vzory a objektové principy 2

Datum konání kurzu:  23. 11. – 25. 11. 2009

Místo konání: Hotel VILLA Praha  Okrajní 1, 100 00, Praha 10

U hotelu VILLA je  možné parkovat, po celý den máme k dispozici wifi připojení.

Na kurzu jsou samozřejmě po celý den teplé a studené nápoje a v ceně kurzu jsou obědy v hotelu.

Podrobné informace o kurzu a možnost přihlásit se na kurz

Program kurzu
Výběr z ohlasů na kurzy



Sunday, 20 September 2009 17:25:11 (Central Europe Standard Time, UTC+01:00)       
Comments [0]  Analytické drobky | Kurzy UML a OOP | Návrhové vzory


 Monday, 27 July 2009
Odchytnutí zprávy WM_KEYDOWN v dialogu – Windows Mobile

V jednom předchozím článku jsem slíbil, že na blog dám i kód, který umožní ve Windows dialogu zachytit všechny stisknuté klávesy. Jak možná víte, dialog ve Windows je běžné okno (Window) s třídou (class) WC_DIALOG. K dialogu je přiřazena speciální funkce WNDPROC, která zajišťuje výchozí zpracování zpráv zaslaných formuláři (např. přechod mezi prvky dialogu pomocí klávesy TAB) a volá vývojářem aplikace určenou obslužnou funkci dialogu (DLGPROC). Jedním z nepříjemných důsledků tohoto modelu chování pro dialogy je, že nejsme schopni v DLGPROC odchytit a zpracovat zprávu o stisknutí tlačítka na klávesnici (WM_KEYDOWN).

Nechceme-li reimplementovat všechny vychytávky dialogů v našem vlastním “okně” (Window) a současně chceme i v dialogu odchytit zprávu WM_KEYDOWN, musíme výchozí WNDPROC obslužnou funkci při vytváření dialogu nahradit naší vlastní “proxy” WNDPROC funkcí.


BOOL CALLBACK DlgProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    if (message == WM_INITDIALOG)
    {
        ChangeDialogWndProc(hWnd);
    }
    
    return true;
}

void ChangeDialogWndProc(HWND hwnd)
{
    g_oldDlgdProc = (WNDPROC)GetWindowLong(hwnd, GWL_WNDPROC);
    SetWindowLong(hwnd, GWL_WNDPROC, (LONG)&DlgWindowsProc);
    
}

V naší  obslužné proceduře dialogu DlgProc při inicializaci dialogu (zpráva WM_INITDIALOG) voláme funkci ChangeDialogWndProc, která zaregistruje naší “proxy” WINDPROC funkci pomocí API SetWindowLong. Ještě předtím si uložíme do proměnné g_oldDlgdProc pointer na předchozí WNDPROC funkci, která je návratovou hodnotou API funkce GetWindowLong, když jí  ve druhém argumentu předáme konstantu GWL_WNDPROC.

V naší “proxy” funkci WNDPROC odchytneme všechny potřebné zprávy a když chceme zachovat výchozí chování dialogu, předáme zprávu ke zpracování  v předchozím kroku uložené výchozí Windows proceduře pro dialogy.

RESULT CALLBACK DlgWindowsProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    if (message == WM_KEYDOWN)
    {
        //Do something with key
        int key = (int) wParam;
    }
    
    return g_oldDlgdProc(hWnd, message, wParam, lParam);
    
}

 

Následuje jednoduchý příklad  založený na standardní šabloně WM projektu.

// HookDialog.cpp : Defines the entry point for the application.
//

#include "stdafx.h"
#include "HookDialog.h"


#define MAX_LOADSTRING 100

// Global Variables:
HINSTANCE            g_hInst;            // current instance
HWND                g_hWndMenuBar;        // menu bar handle
WNDPROC g_oldDlgdProc;

// Forward declarations of functions included in this code module:
ATOM            MyRegisterClass(HINSTANCE, LPTSTR);
BOOL            InitInstance(HINSTANCE, int);
LRESULT CALLBACK    WndProc(HWND, UINT, WPARAM, LPARAM);
void ChangeDialogWndProc(HWND hwnd);
BOOL CALLBACK DlgProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
LRESULT CALLBACK DlgWindowsProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);

int WINAPI WinMain(HINSTANCE hInstance,
                   HINSTANCE hPrevInstance,
                   LPTSTR    lpCmdLine,
                   int       nCmdShow)
{
    MSG msg;

    // Perform application initialization:
    if (!InitInstance(hInstance, nCmdShow)) 
    {
        return FALSE;
    }


    DialogBox(g_hInst, MAKEINTRESOURCE(IDD_POCKETPC_PORTRAIT), NULL, &DlgProc);
    // Main message loop:
    while (GetMessage(&msg, NULL, 0, 0)) 
    {
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }

    return (int) msg.wParam;
}

//
//  FUNCTION: MyRegisterClass()
//
//  PURPOSE: Registers the window class.
//
//  COMMENTS:
//
ATOM MyRegisterClass(HINSTANCE hInstance, LPTSTR szWindowClass)
{
    WNDCLASS wc;

    wc.style         = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc   = WndProc;
    wc.cbClsExtra    = 0;
    wc.cbWndExtra    = 0;
    wc.hInstance     = hInstance;
    wc.hIcon         = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_HOOKDIALOG));
    wc.hCursor       = 0;
    wc.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH);
    wc.lpszMenuName  = 0;
    wc.lpszClassName = szWindowClass;

    return RegisterClass(&wc);
}

//
//   FUNCTION: InitInstance(HINSTANCE, int)
//
//   PURPOSE: Saves instance handle and creates main window
//
//   COMMENTS:
//
//        In this function, we save the instance handle in a global variable and
//        create and display the main program window.
//
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
    HWND hWnd;
    TCHAR szTitle[MAX_LOADSTRING];        // title bar text
    TCHAR szWindowClass[MAX_LOADSTRING];    // main window class name

    g_hInst = hInstance; // Store instance handle in our global variable

    // SHInitExtraControls should be called once during your application's initialization to initialize any
    // of the device specific controls such as CAPEDIT and SIPPREF.
    SHInitExtraControls();

    LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING); 
    LoadString(hInstance, IDC_HOOKDIALOG, szWindowClass, MAX_LOADSTRING);

    //If it is already running, then focus on the window, and exit
    hWnd = FindWindow(szWindowClass, szTitle);    
    if (hWnd) 
    {
        // set focus to foremost child window
        // The "| 0x00000001" is used to bring any owned windows to the foreground and
        // activate them.
        SetForegroundWindow((HWND)((ULONG) hWnd | 0x00000001));
        return 0;
    } 

    if (!MyRegisterClass(hInstance, szWindowClass))
    {
        return FALSE;
    }

    hWnd = CreateWindow(szWindowClass, szTitle, WS_VISIBLE,
        CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL);

    if (!hWnd)
    {
        return FALSE;
    }

    
    ShowWindow(hWnd, nCmdShow);
    UpdateWindow(hWnd);


    return TRUE;
}

//
//  FUNCTION: WndProc(HWND, UINT, WPARAM, LPARAM)
//
//  PURPOSE:  Processes messages for the main window.
//
//  WM_COMMAND    - process the application menu
//  WM_PAINT    - Paint the main window
//  WM_DESTROY    - post a quit message and return
//
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    int wmId, wmEvent;
    PAINTSTRUCT ps;
    HDC hdc;

    
    switch (message) 
    {
        case WM_COMMAND:
            wmId    = LOWORD(wParam); 
            wmEvent = HIWORD(wParam); 
            // Parse the menu selections:
            switch (wmId)
            {
                case IDM_OK:
                    DestroyWindow(hWnd);
                    break;
                default:
                    return DefWindowProc(hWnd, message, wParam, lParam);
            }
            break;
        case WM_CREATE:
            SHMENUBARINFO mbi;

            memset(&mbi, 0, sizeof(SHMENUBARINFO));
            mbi.cbSize     = sizeof(SHMENUBARINFO);
            mbi.hwndParent = hWnd;
            mbi.nToolBarId = IDR_MENU;
            mbi.hInstRes   = g_hInst;

            if (!SHCreateMenuBar(&mbi)) 
            {
                g_hWndMenuBar = NULL;
            }
            else
            {
                g_hWndMenuBar = mbi.hwndMB;
            }

            break;
        case WM_PAINT:
            hdc = BeginPaint(hWnd, &ps);
            
            // TODO: Add any drawing code here...
            
            EndPaint(hWnd, &ps);
            break;
        case WM_DESTROY:
            CommandBar_Destroy(g_hWndMenuBar);
            PostQuitMessage(0);
            break;


        default:
            return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}


BOOL CALLBACK DlgProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    if (message == WM_INITDIALOG)
    {
        ChangeDialogWndProc(hWnd);
    }
    
    return true;
}

LRESULT CALLBACK DlgWindowsProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    if (message == WM_KEYDOWN)
    {
        //Do something with key
        int key = (int) wParam;
    }
    
    return g_oldDlgdProc(hWnd, message, wParam, lParam);
    
}

void ChangeDialogWndProc(HWND hwnd)
{
    g_oldDlgdProc = (WNDPROC)GetWindowLong(hwnd, GWL_WNDPROC);
    SetWindowLong(hwnd, GWL_WNDPROC, (LONG)&DlgWindowsProc);
    
}


Monday, 27 July 2009 11:29:07 (Central Europe Standard Time, UTC+01:00)       
Comments [0]  Compact .Net Framework | Nativní kód


 Tuesday, 09 June 2009
Chyba při používání prvku Popup v Silverlightu 2.0

Tento spot se objevil již před nějakou dobou jako komentář na Zdrojáku, ale protože se s podobným problémem setkal i Michal Kočí na Twitteru, dostal jsem pár emailů s popisem chyby v Silverlightu a znovu jsme narazili na podobné chyby při portaci firemního frameworku, dávám původní komentář i sem na na blog, abych měl kam trvale odkazovat další zoufalce. :)

Jestliže máte vlastni User Control, ve kterém je Popup a tento Popup neobsahuje ListBox (a možná další prvky), je možné Popup zobrazit a používat, aniž by byl přidán do kolekce Children rodičovské “stránky” (třída typicky nazvaná Page  – potomek UserControl) v Silverlightu. Takto definovaný POPUP funguje bez problémů.

<pexeso:popupbase x:class="RStein.Pexeso.SaveFile" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:pexeso="clr-namespace:RStein.Pexeso">

    <grid x:name="LayoutRoot" background="Black">

        <popup name="filesPopup">

            <popup.child>

                <stackpanel orientation="Vertical" background="Red">

                    <textblock text="Název souboru s uloženou hrou" fontsize="15" margin="5,5,5,0" horizontalalignment="Left" foreground="White" textdecorations="Underline"></textblock>

                    <stackpanel orientation="Horizontal">

                        <textbox name="txtFile" margin="5" minwidth="200"></textbox>

                        <textblock foreground="Yellow" visibility="Collapsed" name="txtError" text="Musíte zadat platný název souboru!" horizontalalignment="Left" verticalalignment="Center" fontweight="Bold" fontsize="10"></textblock>

                    </stackpanel>

                    <stackpanel orientation="Horizontal" margin="5">

                        <button style="{StaticResource DialogButton}" content="Uložit" name="btnSelect" click="btnSelect_Click"></button>

                        <button style="{StaticResource DialogButton}" content="Zpět" name="btnBack" click="btnCancel_Click"></button>

                    </stackpanel>

                </stackpanel>

            </popup.child>

        </popup>

    </grid>

</pexeso:popupbase>

 

Jestliže ale Popup obsahuje Listbox (a pravděpodobně i jiné prvky), Popup se zobrazí, ale při vybrání libovolné položky v ListBoxu celý Silverlight plugin spadne do obsluhy události UnhandledException a napíše jen něco o interní fatální chybě. Mimochodem, Bety a RC Silverlightu tímto problémem podle mě netrpěly.

Tento popup způsobí pád Silverlightu, jestliže popup není před svým zobrazením přidán do kolekce Children.

<pexeso:popupbase x:class="RStein.Pexeso.SelectFile" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:pexeso="clr-namespace:RStein.Pexeso">

    <grid x:name="LayoutRoot" background="Black">

        <popup name="filesPopup">

            <popup.child>

                <stackpanel orientation="Vertical" background="Red">

                    <textblock text="Vyberte uloženou hru" fontsize="15" margin="5,5,5,0" horizontalalignment="Left" foreground="White" textdecorations="Underline"></textblock>

                    <textblock foreground="Yellow" visibility="Collapsed" fontsize="10" name="txtError"></textblock>

                    <border cornerradius="20" background="White">

                        <listbox name="lstFiles" background="Orange" height="200">

                            <listbox.itemtemplate>

                                <datatemplate>

                                    <textblock text="{Binding Mode=OneWay}" foreground="White"></textblock>

                                </datatemplate>

                            </listbox.itemtemplate>

                        </listbox>

                    </border>

                    <stackpanel orientation="Horizontal">

                        <button style="{StaticResource DialogButton}" content="Vybrat soubor" name="btnSelect" click="btnSelect_Click"></button>

                        <button style="{StaticResource DialogButton}" content="Zpět" name="btnBack" click="btnCancel_Click"></button>

                    </stackpanel>

                </stackpanel>

            </popup.child>

        </popup>

    </grid>

</pexeso:popupbase>

 

Před zobrazením Popupu tedy musíme vždy přidat Popup do kolekce Children a po uzavřeni Popupu jej případně odebrat.


 

 private void btnLoad_Click(object sender, RoutedEventArgs e)

        {

            SelectFile file = new SelectFile();

            var files = FileAccessComponent.Instance.GetRootFiles();

            if (files.Length == 1)

            {

                return;

            }

 

            file.FileListBox.ItemsSource = files;           

 

            LayoutRoot.Children.Add(file); //Přidat do kolekce

 

            file.DialogClosed += file_DialogClosed; //Vlastní událost pro všechny mé dialogy

            showPopup(file.FilesPopup);

            file.FileListBox.Focus();

        }

 

        void file_DialogClosed(object sender, EventArgs e)

        {

            SelectFile sfDialog = sender as SelectFile;

 

            try

            {

                if (sfDialog.LastResult == DialogResult.OK && sfDialog.FileListBox.SelectedItem != null)

                {

                    m_currentGame = PexesoGame.Load(sfDialog.FileListBox.SelectedItem.ToString());

                    removeButtons();

                    rebindGameData();

                }

            }

            catch (Exception e1)

            {

                Console.WriteLine(e1);

            }

            finally

            {

                LayoutRoot.Children.Remove(sfDialog); //Odebrat z kolekce

                sfDialog.DialogClosed -= saveFileDialog_DialogClosed;

                hidePopup(sfDialog.FilesPopup);

            }

 

        }



Tuesday, 09 June 2009 09:51:20 (Central Europe Standard Time, UTC+01:00)       
Comments [0]  Silverlight


 Friday, 24 April 2009
Drobná poznámka ke kontravariancí delegátů v C#

Předpokládám, že se stejně jako já těšíte na lepší podporu kovariance a kontravariance u rozhraní a delegátů v připravované verzi  C# 4.0. Už dnes se ale dá s existující podporou kovariance a kontravariance u delegátů pěkně kouzlit – pro ty s exaktnějším přístupem ke kódu a vytříbenou terminologií se slovo “kouzlit” v knihách zásadně překládá jako “psát elegantnější kód”. Opakovat základy kovariance a kontravariance u delegátů zde nebudu a všechny ty, kteří sem zabloudili při svém ahashverovském “googlování” nějakého problému volně souvisejícího s probíraným tématem, odkážu na článek v MSDN. :-)

Kovarianci i kontravarianci delegátů používám rád, ale dnes se mi podařilo narazit na potíž, o které si nejsem jistý, že je všeobecně známa. Alespoň já jsem se po chvíli údivu a narůstajícího rozčilení nad tím, že můj dokonalý kód :-) nechce vybíravý kompilátor přijmout a neustále protestuje, musel zbaběle uchýlit ke specifikaci C# 3.0.

 

Takže zde je popis “problému”.

Tento kód asi nikoho nepřekvapí

 public delegate void MyAction<T>(T t);

    
    public class Base
    {
        
    }
    public class Derived : Base
    {
        
    }
    class Program
    {
        static void Main(string[] args)
        {

            MyAction<Derived> del = Test;
            
        }


        public static void Test(Base p)
        {
            Console.WriteLine(p);
            
    }
}

Máme generického delegáta MyAction, jehož instanci s názvem del vytvoříme v metodě Main. Za generický parametr T dosadíme generický argument typu “Derived” , přičemž delegát ukazuje na metodu Test, která přijímá argument Typu Base. Kontravariance zajistí, že tento kód bez problémů projde.

Zkusme udělat mírnou úpravu. Místo metody Test přiřadíme do delegáta del lambda výraz, u kterého explicitně určíme typ argumentu.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ContravarianceTest
{
    public delegate void MyAction<T>(T t);

    
    public class Base
    {
        
    }
    public class Derived : Base
    {
        
    }
    class Program
    {
        static void Main(string[] args)
        {

            MyAction<Derived> a = (Base b) => Console.WriteLine(b) ;
                          
            
        }

    }

}

Kompilátor se tentokrát razantně ohradí proti podobné manipulaci.

Cannot convert lambda expression to delegate type 'ContravarianceTest.MyAction<ContravarianceTest.Derived>' because the parameter types do not match the delegate parameter types    C:\Documents and Settings\STEIN\Dokumenty\CovarianceTest\Program.cs   

A v další chybě své výhrady upřesní.

Parameter '1' is declared as type 'ContravarianceTest.Base' but should be 'ContravarianceTest.Derived'    C:\Documents and Settings\STEIN\Dokumenty\CovarianceTest\Program.cs   

Shrneme-li to, je zřejmé, že při použití lambda výrazu kompilátor na nějakou kontravarianci argumentů zapomene a vyžaduje, aby typ argumentu v lambda výrazu byl identický s typem argumentu u delegáta. Ještě podotknu, že stejné chování s projeví i u negenerického delegáta - public delegate void MyAction(Derived  d);.

Použijeme-li anonymní metodu, kompilátor stále protestuje jako u lambda výrazů.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ContravarianceTest
{
    public delegate void MyAction<T>(T t);

    
    public class Base
    {
        
    }
    public class Derived : Base
    {
        
    }
    class Program
    {
        static void Main(string[] args)
        {
            MyAction<Derived> a = delegate(Base b)
                                      {
                                          Console.WriteLine(b);
                                      };
              

            
        }


    }

}

Cannot convert anonymous method to delegate type 'ContravarianceTest.MyAction<ContravarianceTest.Derived>' because the parameter types do not match the delegate parameter types    C:\Documents and Settings\STEIN\Dokumenty\CovarianceTest\Program.cs    27    35

Parameter '1' is declared as type 'ContravarianceTest.Base' but should be 'ContravarianceTest.Derived'    C:\Documents and Settings\STEIN\Dokumenty\CovarianceTest\Program.cs    27    44

U anonymních metod a lambda výrazů tedy kontravarianci nehledejme. Ve specifikacci C# 3.0 (C# 3.0 specification – sekce 7.14.1) nalezneme popis omezení pro anonymní metody a lambda výrazy.

“If an anonymous function has an explicit-anonymous-function-signature, then the set of compatible delegate types and expression tree types is restricted to those that have the same parameter types and modifiers in the same order. In contrast to method group conversions (§6.6), contra-variance of anonymous function parameter types is not supported. [zvýraznil R.S.] If an anonymous function does not have an anonymous-function-signature, then the set of compatible delegate types and expression tree types is restricted to those that have no out parameters.”

Většina programátorů v C# asi nikdy na podobné omezení nenarazí, a když už ano, vezmou jako fakt, “že to asi z nějakého důvodu nejde”. Mně přijde podobné chování neintutivní a vsadil bych se, že vetšina vývojářů, ať už vědomě či podvědomě, na C# oceňuje, že jde o jazyk, ve kterém na ně nečíhá mnoho záludností nebo nepříjemných překvapení. S přidáváním dalších a dalších rysů do jazyka vzrůstá pravděpodobnost, že některá nová vlastnost začne ovlivňovat způsob použití starší vlastnosti v jazyce a také vzrůstá počet rozdílů mezi konstrukcemi, které na první pohled vypadají stejně, nebo alespoň od nich poučenější vývojář, ačkoli si je vědom některých rozdílů například mezi delegátem ukazujícím na tradiční funkci, anonymní metodu, lamda výraz a složenou lambdu (lambda statement), očekává podobné chování.

Celou poznámku bychom mohli uzavřít dotazem: “Kolik nových vlastností programovací jazyk snese bez šrámů a posléze pořádných zářezů  na pověsti “jednoduchého” jazyka? Tipnul bych si, že empiricky si to budeme moci ověřit, až se začnou na fórech množit zoufalí vývojáři naříkající nad složitostí jazyka, jako se to děje dnes, když nějaká lama poté, co zbastlila při svých hrátkách “skorofunkčnípůlaplikaci” ve VB, Javě či C#, je nucena programovat v C++”? :-) Jde samozřejmě o hyperbolu, vždyť vím, že budoucnost na krásném IT úhoru je otevřena právě pro všechny ty pilné dělníky nové éry, kteří ochotně vygooglují různé nesouvislé fragmenty kódu, jež považují za společný komunitní majetek k instantnímu užití, dále zkombinují několik z nebe spadlých frameworků dohromady a jsou patřičně hrdi na to, že po dlouhé  praxi znají alespoň přibližný význam poloviny klíčových slov v C# či Javě.  Tedy pro ty, kterým třeba kontravariance v programovacím jazyce nikdy chybět nebude. :-)



Friday, 24 April 2009 11:57:02 (Central Europe Standard Time, UTC+01:00)       
Comments [6]  .NET Framework | Compact .Net Framework


 Tuesday, 14 April 2009
Windows Mobile formulář přes celý displej - v nativním kódu

Na MSDN fórech jsem si všiml, že se vícekrát objevil dotaz, jak v nativním kódu vytvořit okno přes celou obrazovku, které se bude chovat jako formulář v Compact .Net Framework aplikaci při nastavení vlastnosti WindowState = Maximized.

API SHFullScreen sice přepne okno do celoobrazovkového režimu, ale při zobrazení SIPu se opět objeví taskbar. Při skrytí SIPu se okno vrátí do celoobrazovkového režimu. První, co mě napadlo, je skrýt samotný taskbar. Idea dobrá, mohli jsme mít jednoduché řešení,  ale autoři Windows Mobile jako již tradičně řekli ne.

Po několika pokusech jsem zjistil, že jediné použitelné řešení představuje změna pozice a velikosti formuláře vždy, když odchytím zprávu WINDOWPOSCHANGED. To celé je korunováno opakovaným voláním SHFullScreen. Na řešení v Compact .Net Frameworku jsem se nedíval, abych si nekazil radost z vyřešeného úkolu, takže netuším, zda autoři CNF používají ještě nějaký další trik.:-)

Níže naleznete příklad, který je založen na výchozí šabloně Smart Device Windows API projektu. Zajímavé části jsou zvýrazněny tučně. Tento postup lze samozřejmě jednoduše použít ve WTL nebo MFC.

Tady se ještě zeptám:

1) Jsou alespoń pro někoho z Vás tyto tipy/FAQ zajímavé? Já sám miluji přecházení mezi nativním a “managed” kódem, ale asi nemá smysl, abych tyto tipy psal na blog, jestliže o nativní kód (již) nikdo nestojí.Potom by stačílo, abych je nechal utopeny ve fóru o mobilních zařízeních, kde poslouží podobným individuím jako jsem já. V zásobě mám například často kladený dotaz, jak ve Windows Mobile dialogu zachytit WM_KEY zprávy. :-)  I když sám si stále programování v (Compact) .Net frameworku bez dobré znalosti nativního kódu nedovedu představit - což je v roce 2009 možná tristní a nečekaná zpráva.:-)

2) A obecnější dotaz – zajímají někoho z vás tipy pro Windows Mobile/Compact .Net Framework? Pro mě, jak asi tušíte, je programování pro WIN Mobile zařízení potěšení, a proto se podobné tipy objevují i na blogu, který píšu hlavně pro zábavu. I když většinu času jsem nyní strávil vývojem v Silverlightu, WPF, WCF a léčením  roztomilých neštovic na zpočátku krásné tváři Linq2SQL, což znamená, že se na blogu se objeví i další témata, která se budou točit kolem návrhu různých typů aplikací a jako bonus odhalíme nejčastější průšvihy spojené s anemickými modely (i model-view-viewmodely :-))aplikací.

// FullScreen.cpp : Defines the entry point for the application. 
// 
 
#include "stdafx.h"
#include "FullScreen.h"  
 
 
#define MAX_LOADSTRING 100  
 
// Global Variables: 
HINSTANCE           g_hInst;            // current instance 
HWND                g_hWndMenuBar;      // menu bar handle 
RECT usedRect;  
// Forward declarations of functions included in this code module: 
ATOM            MyRegisterClass(HINSTANCE, LPTSTR);  
BOOL            InitInstance(HINSTANCE, int);  
LRESULT CALLBACK    WndProc(HWND, UINT, WPARAM, LPARAM);  
INT_PTR CALLBACK    About(HWND, UINT, WPARAM, LPARAM);  
void MakeFullScreen();  
 
void MakeFullScreen(HWND hWnd)  
{  
      
    SetRect(&usedRect, 0, 0, GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN));  
      
    LONG windowWidth = usedRect.right - usedRect.left;  
    LONG windowHeight = usedRect.bottom - usedRect.top;  
 
    MoveWindow(hWnd,   
               usedRect.left,   
               usedRect.top,  
               windowWidth,  
               windowHeight,  
               FALSE);  
 
    //SIPINFO info;  
    //info.cbSize = sizeof(info);  
    //ZeroMemory(&info, sizeof(info));  
    //info.rcVisibleDesktop = usedRect;  
    //  
    //if (!::SipSetInfo(&info))  
    //{  
    //  int error = GetLastError();  
    //  return FALSE;  
    //}  
 
    SHFullScreen(hWnd, SHFS_HIDETASKBAR | SHFS_HIDESTARTICON);  
      
      
      
}
 
int WINAPI WinMain(HINSTANCE hInstance,  
                   HINSTANCE hPrevInstance,  
                   LPTSTR    lpCmdLine,  
                   int       nCmdShow)  
{  
    MSG msg;  
 
    // Perform application initialization: 
    if (!InitInstance(hInstance, nCmdShow))   
    {  
        return FALSE;  
    }  
 
    HACCEL hAccelTable;  
    hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_FULLSCREEN));  
 
    // Main message loop: 
    while (GetMessage(&msg, NULL, 0, 0))   
    {  
        if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))   
        {  
            TranslateMessage(&msg);  
            DispatchMessage(&msg);  
        }  
    }  
 
    return (int) msg.wParam;  
}  
 
// 
//  FUNCTION: MyRegisterClass() 
// 
//  PURPOSE: Registers the window class. 
// 
//  COMMENTS: 
// 
ATOM MyRegisterClass(HINSTANCE hInstance, LPTSTR szWindowClass)  
{  
    WNDCLASS wc;  
 
    wc.style         = CS_HREDRAW | CS_VREDRAW;  
    wc.lpfnWndProc   = WndProc;  
    wc.cbClsExtra    = 0;  
    wc.cbWndExtra    = 0;  
    wc.hInstance     = hInstance;  
    wc.hIcon         = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_FULLSCREEN));  
    wc.hCursor       = 0;  
    wc.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH);  
    wc.lpszMenuName  = 0;  
    wc.lpszClassName = szWindowClass;  
 
    return RegisterClass(&wc);  
}  
 
// 
//   FUNCTION: InitInstance(HINSTANCE, int) 
// 
//   PURPOSE: Saves instance handle and creates main window 
// 
//   COMMENTS: 
// 
//        In this function, we save the instance handle in a global variable and 
//        create and display the main program window. 
// 
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)  
{  
    HWND hWnd;  
    TCHAR szTitle[MAX_LOADSTRING];      // title bar text 
    TCHAR szWindowClass[MAX_LOADSTRING];    // main window class name 
 
    g_hInst = hInstance; // Store instance handle in our global variable 
 
    // SHInitExtraControls should be called once during your application's initialization to initialize any 
    // of the device specific controls such as CAPEDIT and SIPPREF. 
    SHInitExtraControls();  
 
    LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);   
    LoadString(hInstance, IDC_FULLSCREEN, szWindowClass, MAX_LOADSTRING);  
 
    //If it is already running, then focus on the window, and exit 
    hWnd = FindWindow(szWindowClass, szTitle);    
    if (hWnd)   
    {  
        // set focus to foremost child window 
        // The "| 0x00000001" is used to bring any owned windows to the foreground and 
        // activate them. 
        SetForegroundWindow((HWND)((ULONG) hWnd | 0x00000001));  
        return 0;  
    }   
 
    if (!MyRegisterClass(hInstance, szWindowClass))  
    {  
        return FALSE;  
    }  
 
    hWnd = CreateWindow(szWindowClass, szTitle, WS_VISIBLE,  
        CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL);  
 
    if (!hWnd)  
    {  
        return FALSE;  
    }  
 
    // When the main window is created using CW_USEDEFAULT the height of the menubar (if one 
    // is created is not taken into account). So we resize the window after creating it 
    // if a menubar is present 
    if (g_hWndMenuBar)  
    {  
        RECT rc;  
        RECT rcMenuBar;  
 
        GetWindowRect(hWnd, &rc);  
        GetWindowRect(g_hWndMenuBar, &rcMenuBar);  
        rc.bottom -= (rcMenuBar.bottom - rcMenuBar.top);  
          
        MoveWindow(hWnd, rc.left, rc.top, rc.right-rc.left, rc.bottom-rc.top, FALSE);  
    }  
      
    ShowWindow(hWnd, nCmdShow);  
    //MakeFullScreen(hWnd); 
    UpdateWindow(hWnd);  
      
      
    return TRUE;  
}  
 
// 
//  FUNCTION: WndProc(HWND, UINT, WPARAM, LPARAM) 
// 
//  PURPOSE:  Processes messages for the main window. 
// 
//  WM_COMMAND  - process the application menu 
//  WM_PAINT    - Paint the main window 
//  WM_DESTROY  - post a quit message and return 
// 
// 
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)  
{  
    int wmId, wmEvent;  
    PAINTSTRUCT ps;  
    HDC hdc;  
    LPWINDOWPOS pos;  
 
    static SHACTIVATEINFO s_sai;  
      
    switch (message)   
    {  
        case WM_COMMAND:  
            wmId    = LOWORD(wParam);   
            wmEvent = HIWORD(wParam);   
            // Parse the menu selections: 
            switch (wmId)  
            {  
                case IDM_HELP_ABOUT:  
                    DialogBox(g_hInst, (LPCTSTR)IDD_ABOUTBOX, hWnd, About);  
                    break;  
                case IDM_OK:  
                    SendMessage (hWnd, WM_CLOSE, 0, 0);               
                    break;  
                default:  
                    return DefWindowProc(hWnd, message, wParam, lParam);  
            }  
            break;  
        case WM_CREATE:  
            SHMENUBARINFO mbi;  
 
            memset(&mbi, 0, sizeof(SHMENUBARINFO));  
            mbi.cbSize     = sizeof(SHMENUBARINFO);  
            mbi.hwndParent = hWnd;  
            mbi.nToolBarId = IDR_MENU;  
            mbi.hInstRes   = g_hInst;  
 
            if (!SHCreateMenuBar(&mbi))   
            {  
                g_hWndMenuBar = NULL;  
            }  
            else 
            {  
                g_hWndMenuBar = mbi.hwndMB;  
            }  
 
            // Initialize the shell activate info structure 
            memset(&s_sai, 0, sizeof (s_sai));  
            s_sai.cbSize = sizeof (s_sai);  
            break;  
        case WM_PAINT:  
              
            hdc = BeginPaint(hWnd, &ps);  
              
            // TODO: Add any drawing code here... 
              
            EndPaint(hWnd, &ps);  
            break;  
        case WM_DESTROY:  
            CommandBar_Destroy(g_hWndMenuBar);  
            PostQuitMessage(0);  
            break;  
        case WM_WINDOWPOSCHANGED: 
           pos = (LPWINDOWPOS) lParam;  
            if ((pos->cx != usedRect.right - usedRect.left) ||  
                (pos->cy != usedRect.bottom - usedRect.top))  
            {  
                MakeFullScreen(hWnd);  
            }  
            break;
  
        case WM_ACTIVATE:  
               
            // Notify shell of our activate message 
             SHHandleWMActivate(hWnd, wParam, lParam, &s_sai, FALSE);  
            break;  
        case WM_SETTINGCHANGE:  
            SHHandleWMSettingChange(hWnd, wParam, lParam, &s_sai);  
            break;  
 
        default:  
            return DefWindowProc(hWnd, message, wParam, lParam);  
    }  
    return 0;  
}  
 
// Message handler for about box. 
INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)  
{  
    switch (message)  
    {  
        case WM_INITDIALOG:  
            {  
                // Create a Done button and size it.   
                SHINITDLGINFO shidi;  
                shidi.dwMask = SHIDIM_FLAGS;  
                shidi.dwFlags = SHIDIF_DONEBUTTON | SHIDIF_SIPDOWN | SHIDIF_SIZEDLGFULLSCREEN | SHIDIF_EMPTYMENU;  
                shidi.hDlg = hDlg;  
                SHInitDialog(&shidi);  
            }  
            return (INT_PTR)TRUE;  
 
        case WM_COMMAND:  
            if (LOWORD(wParam) == IDOK)  
            {  
                EndDialog(hDlg, LOWORD(wParam));  
                return TRUE;  
            }  
            break;  
 
        case WM_CLOSE:  
            EndDialog(hDlg, message);  
            return TRUE;  
 
    }  
    return (INT_PTR)FALSE;  
}  



Tuesday, 14 April 2009 15:04:43 (Central Europe Standard Time, UTC+01:00)       
Comments [8]  Compact .Net Framework | Nativní kód


 Friday, 20 March 2009
GSM Net Monitor verze 0.10.0

 NMPodrobnostiHomepage aplikace.

Instalační cab.

Návod na rozchození lokalizace pozice pomocí BTS

Paypal donate

Změny ve verzi  0.10.0.

  1. Odstraněna chyba, která se mohla projevit tím, že se v detailech o cell id, které se načítají z csv souborů, nemusely některé informace zobrazit, i když v csv souboru informace byla. Děkuji uživateli "Santana” za report chyby.
  2. Interní změny v  implementaci  “observera” RIL vrstvy.


Důležité:

Před instalací nové verze vypněte v aplikaci sledování sítě. Nejlépe starou verzi také sami deaktivujte v nastavení Today obrazovky a odnistalujte ji přes applet Přidat-Odebrat programy.

Protože se jedná o AlFA preview, doporučuji před instalaci Net Monitoru mít v zařízení např. SPB Pocket Plus a v něm aktivovaný safe boot - jestliže by vám "vytuhlo" zařízení, nebudete muset dělat HR (Hard Reset), protože můžete při startu zařízení dočasně deaktivovat Today pluginy.

Program pro jistotu ani nezkoušejte na zařízení, kde je nahráno TouchFlo, nebo jiný agresivní Today plugin. Riskujete zatuhnutí zařízení a je zbytečné mi potom psát dojemné maily, pokud nejste schopni si předtím udělat zálohu PDA nebo alespoň mít funkční safe-boot.

Po upgradu budete muset pravděpodobně znovu zadat svůj registrovaný email a přístupový kód. Pokud jste jej zapomněli, jděte na stránku http://gsmadmin.renestein.net a zadejte znovu svůj email. Aplikace vám nabídne opětovné zaslání emailu s přístupovým kódem.

Jestliže máte zařízení s VGA displejem, v pluginu jsou malé ikony a plugin je vykreslován na malé ploše. Plugin může být vykreslen na větší ploše  - přes kontexové menu zobrazte Nastavení pluginu a změňte na záložce Základní nastavení preferovanou výšku na obrazovce Dnes. Ikony ale stejně zůstanou malé, proto chystám plnohotnotnou VGA verzi, do té doby lze plugin plně ovládat přes kontextové menu.

NetM1



Friday, 20 March 2009 11:16:56 (Central Europe Standard Time, UTC+01:00)       
Comments [0]  


 Tuesday, 03 March 2009
LINQ a logování na příkladu logování kroků Dijsktrova algoritmu

Na LINQu je pěkné, jak jednoduše můžeme LINQ výraz upravit nebo jej bezbolestně rozšířit o další části. Nedávno jsem publikoval článek Dijsktrův alogritmus pomocí LINQu, extenzních metod a lambda výrazů a nyní si ukážeme drobnou úpravu v kódu, která způsobí, že se před každým rekurzivním voláním vždy vypíšou i prozatímní výsledky hledání nejkratší cesty.

 

Abychom mohli zalogovat výsledek, vytvoříme si vlastní extenzní metody pro výpis informací z předaného libovolného generického IEnumerable<T> do konzole.

static class MiscExtensions
    {
        public static IEnumerable<T> LogToConsole<T>(this IEnumerable<T> source, Func<T, String> logDataSelector, string beginString, string endString)
        {
            if (source == null)
            {
                throw  new ArgumentNullException("source");
            }

            if(logDataSelector == null)
            {
                throw new ArgumentNullException("logDataSelector");
            }

            return innerLogToConsole(source, logDataSelector, beginString, endString);
        }

        public static IEnumerable<T> LogToConsole<T>(this IEnumerable<T> source, Func<T, String> logDataSelector)
        {
            return LogToConsole(source, logDataSelector, null, null);
        }

        public static IEnumerable<T>LogToConsole<T>(this IEnumerable<T> source)
        {
            return LogToConsole(source, (obj => obj.ToString()), null, null);
        }

        public static IEnumerable<T> LogToConsole<T>(this IEnumerable<T> source, string beginString, string endString)
        {
            return LogToConsole(source, (obj => obj.ToString()), beginString, endString);
        }

        private static IEnumerable<T> innerLogToConsole<T>(IEnumerable<T> source, Func<T, String> selector, string beginString, string endString)
        {
            if (beginString != null)
            {
                Console.WriteLine(beginString);
            }

            foreach (var obj in source)
            {
                String val = selector(obj);
                Console.WriteLine(val);
                yield return obj;
            }

            if (endString != null)
            {
                Console.WriteLine(endString);
            }
        }

    }

Metod pro logování máme více, abychom nemuseli pokaždé předat všechny argumenty. Prvním argumentem je vždy zdrojová sekvence, o jejíchž prvcích budou logovány informace. Argument logDataSelector nese odkaz na funkci, která umí z objektu ve zdrojové sekvenci získat jeho textovou reprezentaci. Jestliže delegát logDataSelector není předán, je k získání textové reprezentace objektu použita metoda ToString() zdrojového objektu. Další nepovinné argumenty beginString a endString jsou řetězce, které má extenzní funkce zapsat do konzole předtím, než jsou vypsána data o prvním objektu v zdrojové sekvenci (beginString), a po zalogování všech objektů v sekvenci (endString). V našem případě argumenty beginString a endString  použijeme k vypsání řetězců, které ohraničí jednolivé kroky algoritmu. Naše extenzní funkce je “neinvazivní”, což znamená, že nefiltruje ani nekonvertuje objekty ve zdrojové sekvenci, ale po vypsání informace o zdrojovém objektu je nezměněný objekt příkazem yield return předán k dalšímu zpracování. Předchozí věta obsahuje varování, že nechcete-li se dočkat nepříjemných překvapení, delegát předaný v argumentu logDataSelector by neměl žádným způsobem měnit data zdrojového objektu, ale pouze je pasivně číst.

Celý algoritmus i s podrobným popisem už zde nebudu opakovat, vložím sem jen pro nás zajímavou rekurzivní metodu getShortestPathInner. Podpora logování je jednoduchou úpravou, protože pouze na námi vybraném neuralgickém místě v LINQ výrazu, které chceme špehovat, zavoláme naši extenzní funkci LogToConsole. Pro lepší orientaci je přidaný kód v následujícím výpisu zvýrazněn tučným červeným písmem.

private static IEnumerable<GraphPath<A0>> getShortestPathInner<A0, A1>(IEnumerable<GraphPath<A0>> initialGraphPath, IEnumerable<A0> processed, IEnumerable<A1> edges)
                                                        where A1 : IGraphEdge<A0>
        {
            var candidates = (from node in edges
                              where !processed.Contains(node.From)
                              select node.From).Distinct();

            if (candidates.Count() == 0)
            {
                return initialGraphPath;
            }

            var minimum = initialGraphPath.Where(gPath => candidates.Contains(gPath.Current)).Min(gPath => gPath.TotalDistance);

            var minimumGPath = (from gPath in initialGraphPath
                                where candidates.Contains(gPath.Current) &&
                                      gPath.TotalDistance == minimum
                                select gPath).First();



            var newGraphPath = from cNode in edges
                               where cNode.From.Equals(minimumGPath.Current)
                               select new GraphPath<A0>
                                       {

                                           Current = cNode.To,
                                           Previous = minimumGPath.Current,
                                           TotalDistance = cNode.Distance + minimumGPath.TotalDistance

                                       };



            var newGraphResult =
                                   (initialGraphPath.Concat(newGraphPath).Where(obj =>
                                                            !initialGraphPath.Any(
                                                                                   obj2 => obj2.Current.Equals(obj.Current) &&
                                                                                   (obj2.TotalDistance < obj.TotalDistance))))
                                                                                   .LogToConsole(obj => String.Format("{0} - {1} - {2}", 
                                                                                                                    obj.Previous, obj.Current, obj.TotalDistance),"--Další kolo algoritmu--", "--Konec kola--")
                                                                                   .ToArray();
            






            var newProcessed = processed.Union(new[] { minimumGPath.Current });

            return getShortestPathInner(newGraphResult, newProcessed, edges);

        }
    }

A zde je ukázka, jak vypadá výstup.

image

Logovat nemusíte jen do konzole, ale můžete si přidat další extenzní metody, které zohlední vaše speciální nároky, kam a jak se mají informace o objektech v sekvenci logovat. Cílem článku bylo jen ukázat, jak bezbolestné a hlavně elegantní :-) je přidání logování do stávajících LINQ výrazů.



Tuesday, 03 March 2009 16:34:35 (Central Europe Standard Time, UTC+01:00)       
Comments [0]  .NET Framework | Compact .Net Framework | LINQ


 Monday, 02 March 2009
Náhrada ParametrizedThreadStart delegáta v Compact .Net Frameworku

Na fórech o Compact .Net Frameworku (CNF) se často objevují stesky,  že v CNF třída Thread nemá konstruktor, který by přijímal delegáta ParametrizedThreadStart. Metodě, na kterou ukazuje delegát ParametrizedThreadStart a která bude spuštěna v novém threadu, můžeme předat jeden argument typu object .

public delegate void ParametrizedThreadStartDelegate(Object obj);

 

Ty nářky jsou liché, protože můžeme předat do konstruktoru odkaz na instanční metodu bez argumentů ve vlastním objektu, který má ve svých proměnných nebo vlastnostech stavové informace, které použije instanční metoda poté, co je zavolána z metody Start threadu.

 

Pomocí anonymních metod či lambda výrazů se ale zbavíme nutnosti deklarovat vlastní třídu. Lambda výraz funguje jako adaptér, který převede metodu s jedním argumentem na metodu bez argumentů, kterou očekává konstruktor třídy Thread.

public partial class Form1 : Form   
    {   
        public Form1()   
        {   
            InitializeComponent();   
        }   
  
        private void Form1_Load(object sender, EventArgs e)   
        {   
            int myArg = 10;   
            Thread myThread = new Thread(() => MyThreadMethodWithArgument(myArg));   
            myThread.Start();   
        }   
  
  
        void MyThreadMethodWithArgument(Object obj)   
        {   
            Console.WriteLine(obj.ToString());   
        }   
  
           
    }  

 

Jestliže chcete použít syntaxi velmi podobnou použití delegáta ParametrizedThreadStartDelegate ve “velkém” .Net Frameworku, můžete si napsat vlastní třídu ParametrizedThreadStart, která umožňuje konverzi na delegáta ThreadStartDelegate a tedy opět funguje jako adaptér, který můžeme bez problémů předat do konstruktoru třídy Thread.

using System;
using System.Diagnostics;
using System.Linq;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Threading;
using System.Windows.Forms;

namespace ParametrizedThread
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            int myArg = 10;
            Thread myThread = new Thread(new ParameterizedThreadStart(MyThreadMethodWithArgument, myArg));
            myThread.Start();
        }


        void MyThreadMethodWithArgument(Object obj)
        {
            Console.WriteLine(obj.ToString());
        }

        
    }

    public delegate void ParametrizedThreadStartDelegate(Object obj);
    public class ParameterizedThreadStart
    {
        private ParametrizedThreadStartDelegate InnerDelegate { get; set; }
        private object Param { get; set; }

        public ParameterizedThreadStart (ParametrizedThreadStartDelegate del, object param)
        {
            InnerDelegate = del;
            Param = param;
        }

        public static implicit operator ThreadStart(ParameterizedThreadStart instance)
        {
            return (() => instance.InnerDelegate(instance.Param));
                     
            
        }
        

    }


}

 

Třída ParameterizedThreadStart vyžaduje, abyste do konstruktoru předali argument pro delegáta. Jestliže do konstruktoru argument ihned předat nechcete, ale chcete ve třídě Thread předat argument pro delegáta přetížené metodě Start, tak jako je tomu opět v NF, nezbývá než se na CNF uchýlit k extenzním metodám.

using System;
using System.Collections;
using System.Diagnostics;
using System.Linq;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Threading;
using System.Windows.Forms;

namespace ParametrizedThread
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            
            Thread myThread = new Thread(new ParameterizedThreadStart(MyThreadMethodWithArgument));
            myThread.Start();
        }


        void MyThreadMethodWithArgument(Object obj)
        {
            Console.WriteLine(obj.ToString());
        }

        
    }

    public delegate void ParametrizedThreadStartDelegate(Object obj);
    public class ParameterizedThreadStart
    {
        public EventHandler<EventArgs> MyEvent;
        private ParametrizedThreadStartDelegate InnerDelegate { get; set; }
        private object Param { get; set; }
        private bool IsParamSetInConstructor { get; set; }

        public ParameterizedThreadStart(ParametrizedThreadStartDelegate del, object param)
        {
            InnerDelegate = del;
            Param = param;
            IsParamSetInConstructor = true;
        }

        public ParameterizedThreadStart(ParametrizedThreadStartDelegate del)
            : this(del, null)
        {
            IsParamSetInConstructor = false;
        }

        public static implicit operator ThreadStart(ParameterizedThreadStart instance)
        {

            return (() =>
                        {
                            
                            ThreadExtensions.SetThreadData();                                                        
                            var delArg = instance.IsParamSetInConstructor
                                             ? instance.Param
                                             : Thread.GetData(Thread.GetNamedDataSlot(ThreadExtensions.THREAD_DATA));




                            instance.InnerDelegate(delArg);
                        });
        }
    }
    

                                 


    public static class ThreadExtensions
    {
        public const string THREAD_DATA = "MethodData";
        private static Hashtable _threadDatahashTable = Hashtable.Synchronized(new Hashtable());
        
        public static void  Start (this Thread thread, object val)        
        {
            if (thread == null)
            {
                throw  new ArgumentNullException("thread");
            }
            
            _threadDatahashTable[thread.ManagedThreadId] = val;                                    
            thread.Start();            
        }

        internal static void SetThreadData()
        {
            object val = null;
            val = _threadDatahashTable[Thread.CurrentThread.ManagedThreadId];
            _threadDatahashTable.Remove(Thread.CurrentThread.ManagedThreadId);
            Thread.SetData(Thread.GetNamedDataSlot(THREAD_DATA), val);            
        }
    }
}

Operátor ThreadStart ve třídě ParametrizedThreadStartDelegate vrací složitější lambda výraz, ve kterém dojde k rozhodnutí, zda bude metodě, na kterou ukazuje InnerDelegate předán argument z konstruktoru, nebo argument, který byl předán extenzní metodě Start. Data specifická pro thread jsou v metodě SetThreadData vyzvednuta z objektu Hashtable a uložena v pojmenovaných datových slotech threadu. Extenzní metoda Start používá pro účely tohoto příkladu objekt Hashtable, protože pro Hashtable je  narozdíl od generické třídy Dictionary možné rychle získat její threadově bezpečnou (tedy z větší části threadově bezpečnou :-) ) verzi - Hashtable.Synchronized(new Hashtable()); a náš delegát ParametrizedThreadStartDelegate, přijímající typ object, si stejně na typovou bezpečnost moc nepotrpí. Tyto nevýhody by vás měly přesvědčit, že nejlepší, přímočaré a hacků prosté řešení jsem zmínil na začátku – vytvořte svoji vlastní třídu s instanční metodou a typovými vlastnostmi, které ponesou stavové informace. Další možností může být vytvoření vlastního wrapperu nad nativními API CreateThread a CreateFiber. :-) Chcete-li ale v CNF použít  ve třídě Thread delegáta ParametrizedThreadStartDelegate ve stylu NF, znáte nyní více způsobů, jak to provést.



Monday, 02 March 2009 13:48:05 (Central Europe Standard Time, UTC+01:00)       
Comments [0]  Compact .Net Framework


 Thursday, 26 February 2009
Dlouhodobější zkušenosti s Českou spořitelnou a mBank aneb můj život s českými bankami

Po delší době tímto spotem odbočuji od IT témat na tomto blogu, což snad i stálí laskaví čtenáři snesou bez újmy na svém duševním zdraví. Přesný význam předchozí věty je – tohle je můj blog a nechci v komentářích slyšet, že "tyhle" spoty  sem nepatří a že bych klidně mohl dodat další kód v C#, jak se kdysi stalo. Úvodní opakovací lekci o právech autora blogu a jeho čtenářů máme za sebou. :)

Zkušenosti s mBank po devíti měsících jsem se rozhodl sepsat, protože články o mBank jsou většinou nezajímavé pochvalné ódy, které v podtextu čtenáři sdělují, že je duševně méněcenný či alespoň negramotný v oblasti financí, jestliže své peníze doposud nezakotvil v té úžasné mBank, kde dokonce – velký nádech, přichází pointa – je vše, realističtější autoři napíšou alespoň skoro vše, zdarma. Nevím, jestli bych chtěl žít ve světě těchto rozšafných strýčků Skrblíků, pro které je jediným měřítkem kvality všech věcí cena.

Proč jsem šel zrovna do mBank já? Snad bude vhodné napsat, že jsem byl klasický konzervativní klient, kterého určitě mají naše banky rády, protože poplatky za vedení účtu moc nesleduju, nabídky konkurenčních bank také ne a zajímá mě pouze to, že moje jednorázové i trvalé příkazy odejdou, kdy mají, že mám v ruce platební kartu, kterou mohu použít i pro platby na internetu. Hotovost vybírám jen kvůli platbě v některých restauracích, obchody, kde kartu neberou, pro mě neexistují,  a pokud se v takovém obchodě ocitnu a zjistím, že nemohu zaplatit kartou, odcházím. To se ale nestává moc často, protože nakupuju jen, když je naše rodina přepnuta do nouzového režimu, což znamená, že z nějakého důvodu nemůže praktické záležitosti, včetně pro mě neskutečně otravného nakupování, obstarat Petra. :) Na pobočky chodit nechci, internetové bankovnictví je tou pupeční šňůrou, která mě spojuje se službami mateřské banky, a zálohu pupeční šňůry představuje GSM bankovnictví (Sim Toolkit). Tedy taková ideální ovce pro Českou spořitelnu (dosaďte další velké banky v ČR dle své situace), kterou si Česká spořitelna každý rok slušně ostříhá, aby mohla vykázat větší zisky svým, dnes finanční krizí zbídačelým, :) zahraničním vlastníkům. Česká spořitelna veškeré mé nároky na finanční služby splňuje a záleží jen na úhlu pohledu, jestli poplatky za poskytované služby považujete za součást oboustranně výhodné symbiozy, nebo za parazitování jedné strany. Tím chci říci, že samotná výše poplatků za účet by mě nikdy nedonutila poohlédnout se po jiné bance a je mi jedno, jestli si někdo z akcionářů České spořitelny dopřeje Bentley nebo roční luxusní dovolenou z poplatků.  Podotýkám to jen proto, že z některých obdivných článků o mBank vykukovala ta pravá česká nefalšovaná a ochrannou EU známku si bezesporu zasluhující česká závist. V článcích má závist zastřené rysy, ale v podtextu stále slyším takové to typické důchodcovské stěžování třeba na Radiožurnálu - znáte ne - “já jim platím…poplatky (zajíká se)… k***vam a voni jsou pak na Sejšelách, jako Koženej, co mě taky vokrad, já tady dřu, jak to, že mají víc peněz, než JÁ, eště že si to ten ten náš starej dobrej ranař Paroubek srovná”.

Můj život s Českou spořitelnou se dá rozdělit do tří etap. V první etapě byla Česká spořitelna příšernou institucí, ve které nikdo nikdy nic pořádně nevěděl. I při nějakém triviálním dotazu podrážděné pracovnice z pobočky na malém městě vždy sháněly své kolegyně z “centrály”, aby mohly společně vytvořit pompézní seznam dílčích, nepatřičných, věci se netýkajícících, ale od dalších dotazů klienta odrazujících odpovědí. Předchozí věta plná korektního newspeaku chce říci, že opakované plácání hovadin pracovníky banky bylo výborným způsobem, jak odradit své zákazníky od zbytečných návštěv a na pobočce v malém městě strávit zbytek odpoledne lakováním nehtů, což je rafinovaný druh autoerotické předehry k bujarému večeru, který jim dovolí odplavit ze svého mozku i zbylé vzpomínky na drakonické firemní školení, kde musely za necelý týden pochopit celé dvě stránky nabité shrnujícími informacemi o finančních produktech, které nabízí jejich chlebodárce. Bylo to v době, kdy slovo internet bylo posvátné, proto jsme jej psali ještě všichni s velkým I, a o internetovém bankovnictví v ČS se možná někde na centrále v bankovních kuloárech tiše spekulovalo, ale návštěvě pobočky plné oduševnělých pracovnic jste se určitě nevyhli. Pravěk. V té době jsem poprvé přemýšlel o změně banky, ale nakonec jsem u ČS zůstal.

Přišla totiž etapa dva, na kterou nostalgicky vzpomínám. Nevím, jestli se fáze dva kryje se vstupem zahraničního vlastníka, ale najednou bylo vše jinak. První dobrý signál – z malých měst zmizely pobočky, protože banka začala preferovat telefonické, poté internetové a sim toolkit bankovnictví. Pobočka ve větším městě fungovala k mé plné spokojenosti. Žádné velké fronty, když už jsem na pobočku musel zajít. Požádal jsem o embosovanou kartu a slečna věděla, o jaký produkt jde, nabídla mi ke kartě další služby a ihned mi dala písemné podmínky používání těchto služeb. Nesnažila se nic zatajit ani okecat. Sdělila mi, kdy karta přijde, a poprvé opravdu karta dorazila ve stanoveném termínu. Byl to snad bankovní pud sebezáchovy, který se u ČS aktivoval, protože když jsem šel žádat o embosovanou kartu, oznámil jsem doma, že jestli nastanou nějaké potíže, tak s ČS končím a jdu jinam. Nestalo se a já jsem si užíval etapy dva mého vztahu s bankou, kdy se z ČS někomu schopnému ve vedení podařilo vybudovat docela příjemnou banku. Jak vidíte, žádné velké nároky na služby banky nemám.

Nic na tomto světě netrvá věčně, dobré věci už vůbec ne, a ani Česká spořitelna nezůstala v té příjemné etapě “dva” dlouho. Nastala etapa tři, která trvá dodnes a kterou jsem si pojmenoval podle tajného prvního axiomu, který si musí jako základ firemní kultury asi osvojit všichni noví zaměstnanci. Axiom zní: “Všichni naši klienti jsou debilní, my to víme, podle toho s nimi jednáme a tak je chráníme.” Tento axiom byl podrobně rozpracován v prováděcích příručkách, které popisují hlavní zásady chování zaměstnance ČS vůči zákazníkům. Takže vybrané lapsy České spořitelny. Vždy říkám, že potřebuji kartu k platbám na internetu. Jaké bylo moje překvapení, když circa před dvěma lety zničehonic neprošla platba kartou za notebook na Alzasoftu. Poté, co jsem zavolal telefonickému bankéři, nebo jak honosně se ti operátoři dnes nazývají, jsem se nejprve dověděl, že slečna je na lince "nová". Mě, jakožto klienta, tyto hlášky ve stylu “jsem ještě dočasně neschopný” nemusí zajímat, to ať si ošetří sama banka, koho vyšle ke klientům. Bezděky mi v mozku vytanula asociace - konverzace se slečnami z poboček v etapě jedna – deja vu. Po několikaminutovém oboustranném trápení mi slečna sdělila, že kvůli mé bezpečnosti (sic) mi byly sníženy limity na internetové platby a že si je mohu zvednout na pobočce. Stejná situace se opakovala asi za dalších devět měsíců – jsme těhotní péčí o zákazníka a každých devět měsíců na něj vyvrhneme nějaké moudré ochranné opatření ze svého bezpečím oplývajícího a nápady nabitého mateřského finančního lůna by mohl znít úderný axiom číslo dva firemní powerpointové kultury. Pochopil jsem ale, proč to ČS dělá – při druhé návštěvě agilní slečna na pobočce, která mi oficiálně zvedala limity, ale neoficiálně mě spíš zvedala ze židle, protože mně svým milým dětským hláskem, který ve svých občasných nočních můrách slyším i dnes,  sdělovala, že musím platit jen tam, kde internet má “zámeček”. Když jsem se jí zeptal, jestli myslí “https”, nastalo trapné ticho a poté opět zašeptala, že tam musí být určitě zámeček. Slečnu jsem upozornil, že vím, co je to https, že běžně nenakupuju erotické služby na nějakých pochybných serverech, jak to pravděpodobně činí majorita jejich zákazníků, kvůli kterým stále ”upravují” limity, a že ještě jednou mi limity upraví a uvidí mě poté jen při zrušení účtu. Neberte to, podrobné školení o bezpečnosti na internetu od pěkné slečny zadarmo, škoda, že se toho nechytl nějaký jurodivý “hasalíkovec” (viz Google – heslo Radim Hasalík pro neznalé :-) ), který by na Lupu napsal další článek o blahodárném vlivu marketingu na chod virtuálního i reálného světa.

Další lapsy v rychlosti. Česká spořitelna mi sdělila, že při pravidelném obnovování karty budu mít novou kartu na pobočce vždy měsíc před vypršením platnosti staré karty, což byla zase jen pravda z PowerPointu, tedy hezkými obrázky a animacemi přibarvená lež.  Třikrát mě pracovníci ignorovali se žádostí o podrobnější konzultaci některých finančních služeb – průběh byl vždy stejný. Mám zájem (třeba) o BrokerJet, ano, kolegyně, která tomu rozumí, zde dnes není, příští týden vám zavolá a domluvíte si schůzku. Uběhl týden, měsíc, rok a kolegyně nezavolala. Pravděpodobně šlo o kolegyni, která nikdy nepřijde – poslední hit, udržujte své zákazníky v nejistotě, pořiďte si náš poslední model zaměstnance vyvíjený pod kódovým názvem Godot. K účtu mi automaticky při obnovení debetní karty pracovnice vydaly kreditní kartu jako dárek “zdarma” – je to moje hloupost, podmínky jsem neověřoval, kartu jsem si vzal a po roce si ČS strhla roční poplatek za vedení karty. Stačilo říct – karta je zdarma na rok a ne říkat, že jde o dárek pro klienty. Stejně bych si tu kreditku vzal. Nedávno mi volala paní, která chtěla, abych zašel na pobočku a pořídil si výhodnou chytrou kreditní kartu. Výhodná tato karta je, chybí bohužel dodatek, že hlavně pro Českou spořitelnu. Petru zaměstnanci ČS lámali, ať přejde na Osobní účet. Nabídli jí nevýhodné podmínky oproti předchozímu tarifu a přitom se dušovali, kolik ušetří. Axiom 1 potvrzen – jsme pro ně všichni nesvéprávní debilové, co neumí počítat.

Když přišla mBank, zkusil jsem si u ní založit účet. To, co mě nalákalo, byla slova, že jde o internetovou banku, tedy banku, která považuje stejně jako já internet za nejdůležitější pojítko mezi mnou a bankou, což by mělo mít pár přijemných důsledků. Nejen běžná správa peněz na účtu, ale také všechny žádosti o další služby a změny v současném nastavení služeb půjdou přes internetové bankovnictví. Když už bude nutné podepsat nějaké věci osobně, mKiosky by měly fungovat i v době, kdy mám čas na řešení techto věcí, a ne jako u velkých bank, u jejichž otevírací doby mě vždy jen napadá: “Pro koho je asi tak určena otevírací doba od 9:00 – 16:00?” Banka navíc nabídla účet zdarma, spořicí účet se slušným úrokem a dokázala kolem sebe udělat pořádný rozruch ještě před tím, než vstoupila na trh. Zdůrazním, že nulové poplatky jsem bral a stále beru jako příjemnou, ale určitě ne klíčovou vlastnost bankovního účtu.

Žádost o zřízení účtu jsem vyplnil ihned po vstupu banky na trh někdy v prosinci 2007, ale protože banka nestíhala odbavovat žádosti, nakonec jsem si účet založil až někdy v dubnu-květnu 2008, kdy jsem očekával, že hlavní procesy v bance již fungují. Nezvolil jsem založení účtu přes kurýra, protože k podepisování smluv o zřízení účtu zajišťuje pro mBanku jiná firma a neměl jsem moc důvěry k lidem, které si banka pravděpodobně sama neškolí.

Účet jsem založil ve finančním centru Vinohradská. Příjemnou změnou bylo to, že finanční centrum nemá sadu kukaní zvaných přepážky jako moje předchozí banka. Po vstupu do banky jsem řekl, co chci, že žádost o zřízení účtu jsem již vyplnil přes internet, a poté jsem chvíli čekal na přidělenou paní-slečnu, která se mnou měla podepsat smlouvu. Pobavilo mě jen, že slečna se po příchodu dotazovala kolegy - kdo že to tedy čeká na zřízení účtu - šeptem, který se pravděpodobně naučila na nějaké herecké škole, kde jí vtloukli do hlavy zásadu, že když se na jevišti šeptá, musejí šeptání slyšet i zadní lavice. Snad už dnes ji kolegové vysvětlili, že (m) Banka není tyjátr.:) Diskrétní zóny nejsou zase tak špatný vynález. Při podepisování smlouvy žádné problémy nenastaly – slečna byla zdvořilá, i když při některých dotazech trochu nejistá. Překvapilo mě, že vůbec neví, jak přesně probíhá zaúčtování platby provedené platební kartou mBank v cizí měně a jaký skutečný význam nese v mBank slovo zdarma, :-) což se tenkrát intenzivně probíralo i na mFóru. Podrobnosti o této hře mBank jsou na webu Vúčako, ke cti mBank slouží, že se tato informace objevila posléze na oficiálním webu. Účet byl zřízen ihned poté, co jsem opustil budovu mBanky, a stačilo jej aktivovat. Internetové bankovnictví mBank bylo popisováno už mnohokrát, zde jen řeknu, že mně jeho strohost a jednoduchost vyhovuje, s jeho ovládáním problémy nemám, i když je to asi hlavně tím, že jsem deformován svým povoláním. Jestliže jako vývojář prohlásím, že mi uživatelské rozhraní vyhovuje, mělo by to být pro lidi z mBank varování, že pro běžného Frantu uživatele může být ovládání složité a překombinované.:-) Určitě jsou některé volby nelogické, zakládání šablony, kdy jste do poslední chvíle nevěděli, zda jste jen založili nebo změnili šablonu platebního příkazu, nebo již byla první platba na základě šablony provedena, patří mezi jedny z vybraných lahůdek, kterým bylo na mFóru věnováno hodně vláken.

Ihned po založení účtu jsem se rozhodl, že Petru učiním spoluvlastníkem účtu. Mimochodem, Česká spořitelna zřídí k účtu disponenta, ale když jsem chtěl, abychom byli vlastníky účtu oba, tak mi bylo řečeno, že to nejde a že na vině je česká legislativa, která to neumožňuje. MBank asi našla dle ČS skulinu v českých zákonech. :) Tady poprvé jsem si uvědomil, že u mBanky nesmíte vybočit z rámce “typického” průběhu nějakého procesu. První problémy nastaly při podávání informací – už při zakládání účtu na finančním centru mi slečna řekla, že manželka se musí osobně podepsat v bance a že mám předtím ještě zavolat na mLinku, aby pro ni rovnou připravili aktivační balíček. Na infolince mi nejprve nějaký operátor po mém dotazu položil hovor, tedy poprvé jsem si myslel, že hovor “vypadl”, ale poté jsem zjistil, že jde o takovou zábavnou hru infolinky, když operátor něco neví. Poté jsem se dostal k operátorovi, který rezolutně prohlásil, že osobní návštěva není nutná, že si vezme informace o manželce po telefonu, poté pošle na naši adresu smlouvu, kterou podepíšeme a pošleme zpět. Měl pravdu, osobní návštěva nutná nebyla. Smlouva dorazila v dalším týdnu, poté aktivační balíček a vše probíhalo hladce. Do té doby, než jsme zkusili aktivaci balíčku. Kdykoli jsme se pokusili o zprovoznění uživatelského účtu, dostávali jsme nádhernou hlášku “chybí seznam jednorázových hesel”. Uznejte sami, že ta hláška vypadá, jako když teď  momentálně nefunguje správně infrastruktura internetového bankovnictví. Chvíli jsme to nechali a poté začala anabáze s infolinkou.  První volání – operátor pokládá telefon. Druhé volání  - dnes nic nedělejte, víme o chybě v internetovém bankovnictví, zítra vše poběží. Druhý den dostáváme stejnou hlášku, další volání na mLinku, obligátní položení telefonu už bereme jako zpestření všedního dne od moderní banky s novým přístupem ke klientům, při dalším hovoru operátor tvrdí, že asi potřebujeme nový aktivační balíček, protože předchozí zasmrádl, pardon propásli jsme doporučené datum aktivace, což je evidentně nesmysl. Zkouším mFórum, mezitím Petra znovu zkusí infolinku. Při dalším zavolání mluví s člověkem, který účinnou taktiku “nevím, položím telefon” vylepšuje na “huhlám si něco směsí slovenštiny, polštiny a maďarštiny” a ani na několikeré slušné upozornění, že mu není rozumět, zásadně nereaguje.  Při dalším telefonátu Petra konečně narazí na operátora, který řekne, že pravděpodobně chybí/je špatně zadán kontaktní telefon a že to ověří. Voila – během chvíle je účet aktivní. Když znáte příčinu, řešení je triviální. Pro ty z vás, kdo uvažují o společném účtu u mBanky by mohlo být zajímavé, že každá fyzická osoba v ČR může mít pouze jedno mKonto a při společném vlastnictví jednoho účtu si vaše manželka další mKonto nezaloží. Jestliže je manželka v roli disponenta, tak si může zřídit svoje vlastní mKonto.

Předchozí odstavec obsahuje popis tradičního scénáře mBanky, když nastanou potíže. To je hlavní důvod, proč najdete na mBanku na mFóru nejen nadšené, ale dnes i velmi kritické reakce. Když vše běží podle zažitých procesů, nikdo si nestěžuje. Když nastane problém, buď narazíte na někoho schopného na infolince, který problém ihned vyřeší, anebo na neschopného ignoranta, a podle toho už mBanku vnímáte. Nepříjemné je, že tyto problémy nevyřešíte přes internet, ale musíte volat na infolinku.

Také uvádění nových služeb je tragikomedie. Minulý rok jsem uvažoval, že nahradím kreditní kartu České spořitelny kreditní kartou mBank. Uvádění produktu na mFóru jsem sledoval jen letmo, ale celkově bych to shrnul – v srpnu vzrůstá očekávání, v září mBank řeší, kdy nejlépe zveřejnit nové obchodní podmínky kvůli konkurenci, no a někdy v prosinci první odvážní začali žádat o karty se všemi peripetiemi, jakou byla speciální edice mBank kreditni karta, model chytrá horákyně, která je současně aktivovaná-neaktivaná.

Chuť dělat opakovaně testera mBance u kreditky mě přešla totiž poté, co jsem požádal o výměnu své nečipové debetní karty k mKontu za čipovou a embosovanou kartu, kterou mBank nabízí nově od ledna 2009. Prý chybou dodavatele ale došlo k tomu, že lidem, kteří si požádali o výměnu někdy na začátku ledna 2009 došla karta sice čipová, ale pro jistotu neembosovaná. Už jsem si říkal, že mBank, stejně jako ČS, ví, co je pro její klienty nejlepší, ale mBank chybu uznala a kartu vyměnila. Nakonec kartu vyměnila, ale předtím se opět ukázaly veškeré nešvary mBanky. Když lidé z mBanky na mFóru (mimochodem nepřipadá vám něco shnilého ve spojeném finančním království česko-polském, když špatné karty dostanou od dodavatele klienti a neprověří je nejprve někdo v bance, zvláště když embosované čipové karty představují nový produkt?), zjistili, že karty jsou neembosované, napsali, že VŠICHNI klienti automaticky obdrží embosované karty. Pro mě ideální řešení. Posléze ale mBanka usoudila, že vyjde vstříc těm klientům, kteří preferují neembosovanou kartu a všech klientů se bude ptát, zda opravdu chtějí embosovanou kartu, nebo zda si raději ponechají neembosovanou kartu. Dozvěděl jsem se to jaksi mimochodem někde na mfóru, když mi někdy v pátek odpoledne volalo nějaké podivné číslo z polské telefonní sítě, kterému se nedalo zavolat nazpět a mě napadlo, že to pravděpodobně byl někdo z mBanky. Poté byly slíbeny omluvné emaily - tyto emaily dorazily  klientům, kteří si dle všech indicií kartu sice objednali na začátku ledna, ale roku 2008, a ne roku 2009. Nádherný exemplář neschopnosti a ukázka nedostatečné kontroly procesů na všech úrovních. Programátor vytáhl žádosti z databáze, v SQL příkazu se sekl o rok a někdo hromadně odeslal emaily, aniž by se pravděpodobně obtěžoval spárovat pro kontrolu některé emaily s lidmi, kteří na mFóru napsali, že jim dorazila neembosovaná karta. Poté jsem volal na mLinku a dohodl jsem se s operátorem, že o výměnu karty zájem mám. Pak mi volal pan Ressler z mBanky, který si vše znovu ověřil a omluvil se. Pomyslnou třešničkou byl telefonát za tři dny nato opět z mBanky, kdy se mě nějaký brigádník ptal, zda chci kartu vyměnit. To je péče. Asi dobře věděl, proč hned na začátku hovoru upozorňuje, že hovor může být nahráván, protože i já jsem už zvýšil hlas, když se mě ptal na vydání karty, o které jsem si myslel, že je na cestě. Brigádník mi vysvětlil, že na papíře má pouze informaci, že se mi poprvé nedovolali a že moji zadanou žádost znovu prověří. Konec dobrý, embosovaná karta po týdnu dorazila, ale že by platilo i “všechno dobré”?

Při běžném používání účtu je mBank z mého pohledu bezproblémová. Jednorázové platby jsou rychlé, s nečipovou kartou mBank jsem minulý rok neměl problémy při žádné platbě ani v ČR ani v zahraničí narozdíl od některých jiných klientů, kteří si stěžovali na  mFóru. Z účtu v mBance platíme naše běžné měsíční výdaje. Příjemné jsou tři výběry v každém měsíci z libovolného bankomatu v ČR zdarma u každé platební karty k účtu. Spořicí účet eMax má nyní sazbu 2,8%. I když to není nejlepší sazba na trhu, tak možnost kdykoli vybrat peníze ze spořícího účtu se hodí.

Účty v České spořitelně jsem si ale prozatím ponechal, protože mBance v některých záležitostech moc nevěřím. Jestliže chcete mít klidné spaní, raději nečtěte mFórum. Chyby, které nahlásili klienti po nasazení některých nových verzí internetového bankovnictví, a to i když si odmyslíte příspěvky od tradičních provokatérů a rodilých idiotů, kteří se zde vyskytují v hojném počtu, působí děsivě. Nedovedu si představit, že mBanka se rozhodne udělat nějaké změny v internetovém bankovnictví a mně ve stanovenou dobu neodejdou peníze za nájem nebo finančnímu úřadu. Určitě bych si tedy u mBank nezřizoval podnikatelský mBusiness účet. Už jen proto, že na účtech mBank se špatně identifikují platby a že data z mKonta nelze rozumně vyexportovat. Stačí se podívat na to, jak zmršený je csv formát. Jestliže uvažujete o mPůjčce, je možné, že vás mBank odmítne, i když jste perspektivní klient, protože jste si nikdy nepůjčili a nemáte záznam v registru. Osobní zkušenost s hypotékou nebo mPůjčkou nemám, soudím jen podle opakovaných dotazů na mFóru.

Několikrát jsem zde zmiňoval mFórum. To představuje silnou i slabou stránku mBanky. Na první pohled je mFórum výjimečné tím, že si v něm můžete postěžovat a že vám odpoví někdo z mBanky nebo mRady. Je jasné, že klienti málokdy píší o svých dobrých zkušenostech, ale ventilují na mFóru hlavně ty špatné. Při čtení mFóra je proto stále potřeba mít na mysli, že jednotlivé příspěvky nepopisují skutečnou tvář banky, ale spíš její průšvihářskou odvrácenou stranu, kterou má každá firma, ale většina firem průšvihy raději kamufluje výroky svých PR Pýthií. Možná to může znít paradoxně, ale mFórum mBance asi také prospívá při zachování její dobré pověsti. Proč? Je to takový kanál, do kterého se svede prvotní rozladění klienta, špinavé prádlo se pere tedy doma, a ne na nějakém jiném internetovém fóru třeba u finančního serveru. A lidé z mbanky mají šanci problém nějakou nestandardní cestou vyřešit a alespoň zmírnit naštvání nabroušeného zákazníka. Ale je tohle dobrý postup? Opravdu chcete být v bance, kde vám lidé píší – máš problém s mKioskem, mLinkou, napiš Adamovi Zbiejczukovi (taková šedá eminence internetových aktivit mBanky)  :) a ten tvůj problém vyřeší, sežene vhodné lidi atd.? Kde místo absolvování nějakého standardního procesu mnohdy rychleji dosáhnete kýženého výsledku přes nějaké mFórum?

Mohl bych popisovat i další věci kolem mBanky nebo podrobně rozebrat, jak si z mého pohledu mbanka vede v dialogu se zákazníky po roce. Závěr je ale zřejmý – o mBance se mluví jako o internetové bance a kvůli tomu jsem si u ní založil účet. Internetovou bankou je pro mě banka, která koná podle pravidla – přes internet se dá vyřídit vše, pozveme si vás jen na schůzky, kde musíte třeba i ze zákona prokazovat svoji totožnost. Nové produkty objednáváte ale přes internet, změny v nastavení služeb děláte přes internet, máte-li problém, napíšete nám email nebo zadáte dotaz v internetovém bankovnictví. K tomu má mBanka opravdu daleko – mBanka je hlavně nízkonákladová banka a internet je vhodným prostředkem pro udržení nízkých nákladů. Produkty a služby ale u mBanky mnohdy nezměníte/neobjednáte jinak než přes mLinku, kdy se s vámi o náklady banka částečně podělí. Hledáte-li nízkonákladovou banku, tedy chcete platit co nejnižší poplatky, je mBanka správná volba. Hledáte-li čistě internetovou banku, možná bych se být vámi poohlédl i jinde. Občas s nadsázkou přirovnávám mBanku k Lidlu, i když je to asi nespravedlivé k oběma společnostem. Sám jsem tedy v Lidlu nebyl, protože po prvním a současně posledním nákupu Petry v Lidlu, jsme se oba shodli, že umřít se dá i jinak a lépe než z mizerného jídla. :) MBank narozdíl od Lidlu používám stále. Přesto si myslím, že buď mBanka něco udělá se svými procesy, uvede další zajímavé produkty a potom jí počet klientů stále poroste, nebo po nějakém megaprůšvihu dostane etiketu banky, kde si zřizují účet jen studenti nebo ti, co mají hluboko do kapsy a zaměstnavatel nebo sociálka  po nich požaduje účet, ale nikdo, kdo má zájem i o (klidně placené) kvalitní nadstandardní služby, váží si svého času a nechce se dohadovat ani s nějakou neschopnou pipinou na přepážce u konvenčních bank ani se zhmotněním neschopnosti v podobě operátora na mLince. Můj názor je, že k oběma alternativám má mBank dnes stejně blízko.:-))



Thursday, 26 February 2009 15:28:57 (Central Europe Standard Time, UTC+01:00)       
Comments [9]  Ostatní


 Thursday, 19 February 2009
Dvě uvolněná místa na veřejném kurzu Pokročilé objektové principy a návrhové vzory 2

Dva účastníci kurzu Pokročilé objektové principy a návrhové vzory 2 zvolili dodatečně inhouse variantu školení. I ti z Vás, kteří teď v posledním týdnu dostali informaci, že kurz je předběžne obsazen, se mohou znovu přihlásit.

Rozcestník:

Pozvánka na jarní kurzy

Podrobné informace o kurzu a možnost přihlásit se na kurz

Výběr z ohlasů na předchozí kurz



Thursday, 19 February 2009 12:45:55 (Central Europe Standard Time, UTC+01:00)       
Comments [0]