23/12/2008

Expression Studio

Home

W zeszłym tygodniu (dokładnie 17 grudnia) miałem przyjemność uczestniczyć w spotkaniu pod tytułem Nowoczesny interfejs użytkownika, oraz aplikacje web’owe - technologie i narzędzia Microsoft zorganizowanym przez firmę Microsoft. Uczestnictwo w tym spotkaniu naprawdę było przyjemnością, szczególnie jeżeli porównam je do Microsoft Technology Summit 2008, które według mnie było organizacyjną porażką, a nawet określił bym je pogardliwym mianem tzw. masówki. Spotkanie zorganizowane było w siedziby firmy Microsoft w Warszawie, a poprowadził je Artur Żarski (Developer Evangelist). W tej mini konferencji uczestniczyło około 60 osób. Prelekcja została przeprowadzono bardzo sprawnie i generalnie była ciekawa. Prelegent omówił pięć narzędzi wchodzących w skład Expression Studio:
  • Expression Media Encoder - obrabianie filmów
  • Expression Media - zarządzanie multimediami
  • Expression Web - projektowanie stron WWW
  • Expression Blend - projektowanie interfejsu użytkownika
  • Expression Designer - tworzenie grafiki wektorowej i rysunków
Dokładną charakterystykę i opis tych produktów znajdziemy na tej stronie.

Dla mnie najbardziej interesujące była cześć poświęcona narzędziom Expression Web oraz Expression Blend dlatego poświęcę im jeszcze kilka słów. Na początek należy stwierdzić, że te dwa narzędzia (zresztą pozostałe trzy podobnie) nie są przeznaczone dla programistów, a przynajmniej nie powinny być używane przez programistów. Nie powinny w tym sensie, że zalecane jest aby interfejsy użytkownika były projektowane przez wyznaczonych do tego projektantów. Oczywiście wiem jakie są realia. Napisałem tutaj o sytuacji idealnej.

Ogólnie zaleca się aby osoby/zespoły pracujące na interfejsem użytkownika były niezależne (odseparowane) względem osób/zespołów tworzących właściwą aplikację. Po pierwsze oba zadania wymagają zupełnie innych umiejętności. Po drugie dzięki takiemu podejściu osiągamy stan, w której projektanci interfejsu użytkownika nie przeszkadzają programistą i vice versa. W szczególności unikamy problemy pod tytułem: Kto zmienił nazwę tej kontrolki? Podobnie jest przy tworzeniu gier komputerowych. Modele postaci, bitmapy tworzone są przez wyspecjalizowanych grafików. Efekt ich pracy otrzymują programiści, którzy ożywiają modele, wyświetlają bitmapy, dodają efekty specjalne: oświetlenie, cienie...

Powstaje pytanie co należy stworzyć najpierw. Moim zdaniem należy pamiętać, że interfejs użytkownika to nie tylko wygląd kontrolek, ikony, kolory itd. ale również ergonomia, odpowiednie rozłożenie kontrolek i organizacja przepływu sterowania. Wydaje się, że elementy te powinny zostać zdefiniowane w pierwszej kolejności tak aby programiści znali je już na samym początku pracy. Unikamy potrzeby przebudowy kodu na późniejszym etapie pracy, a w szczególności unikamy sytuacji, w której nad tym samym elementem interfejsu pracuje równocześnie programista i projektant UI. Dlatego jestem zwolennikiem podejścia, w którym interfejs, a już napewno jego projekt/opis, powstaje najpierw.

Jeśli chodzi o Expression Web to godny zauważenia jest fakt, że Microsoft zauważył PHP i dostarczył narzędzia wspierającego ten język. Zaskoczył mnie natomiast fakt, że w przypadku aplikacji ASP.NET nie jest wspierany model code behind!.

Trochę głupio przyznać ale zaskoczyła mnie również informacja, że Expression Blend to aplikacja komercyjna (czytaj płatna). Do tej pory korzystałem bowiem z darmowej wersji community technology preview i miałem nadzieję, że ten stan utrzyma się w wersji finalnej. Szkoda, bo aplikacja ma ogromne możliwość i umożliwia wyczarowanie naprawdę fajnych efektów przy użyciu XAML'a - niektórych rzeczy nie da się po prostu zrobić czysto programistycznie, no chyba, że ktoś potrafi wyobrazić sobie wszystko w głowie.

15/12/2008

Domknięcie - Jak to działa?

Home

Domknięcie (ang. Closure) to cecha języków programowania, która pozwala funkcji odwołać się to zmiennych zdefiniowanych poza jej ciałem. Domknięciem nazywa się również obiekt (byt) wiążący funkcję z środowiskiem jej wykonania. Oczywiście chodzi o zmienne inne niż globalne. Takie powiązane zmienne w języku angielskim określane są mianem bound variables. Przykład domknięcia został przedstawiony poniżej. Proponuję przyjrzeć się temu kodowi i odpowiedzieć co zostanie wypisane na ekran. W dalszej części posta będę używał terminu metoda zamiast funkcja, który bardziej pasuje do obiektowego języka jakim jest C#.
delegate void Fun();
...
List array = new List();
for (int i = 0; i < 5; ++i)
{
  array.Add(delegate() { Console.WriteLine(i); });
}

for (int i = 0; i < 5; ++i)
{
  array[i]();
}
Implementacja domknięcia zależy od konkretnego języka programowania. Mnie oczywiście zaciekawiło jak to zostało zrealizowane w C# i tym zajmę sie w tym poście. W rozwiązaniu zagadki pomógł oczywiście reflektor Lutz Roeder’s Reflector, o którym wspominałem już wcześniej.

Implementacja domknięcia w języku C# bazuje na automatycznie generowanej przez kompilator klasie. Klasa ta zawiera kod metody oraz powiązane z nią zmienne. Dla naszego przykładu klasa ta będzie wyglądać jak poniżej:
[CompilerGenerated]
private sealed class DisplayClass
{
  public int i;

  public void b_0()
  {
    Console.WriteLine(this.i);
  }
}
Kod zmieniłem tak aby był bardziej czytelny. Jak widać wygenerowana klasa jest trywialna. Zawiera jedną metodę, której zawartości odpowiada zawartości zdefiniowanej przez nas anonimowej metody oraz jedno publiczne pole, które stanowi nic innego jak powiązaną zmienną. No dobrze, ale kto korzysta z wygenerowanej klasy i kto ustawia wartość publicznego pola i. Odpowiedź nasuwa się sama - oczywiście kompilator. A jak? Zostało to schematycznie przedstawione poniżej:
DisplayClass d = new DisplayClass();

List array = new List();
for (d.i = 0; d.i < 5; ++d.i)
{
  array.Add(d.b_0);
}

for (int i = 0; i < 5; ++i)
{
  array[i]();
}
Kod ten może trochę dziwić. Od razu nasuwa się pytanie, czemu utworzono tylko jedną instancję automatycznie wygenerowanej klasy? Wszystko się jednak zgadza. Domknięcie działa w ten sposób, że anonimowa metoda ma dostęp do wartości zmiennej powiązanej z czasu jej wykonywania, a nie z czasu kiedy została utworzona. Nie trudno zauważyć, że w momencie wywoływania metody wartość zmiennej i wynosi 5. Odpowiedź na postawione przeze mnie wcześniej pytanie brzmi, więc: Na ekran zostanie wypisany ciąg: 5 5 5 5 5. Poniżej podaję sposób w jaki osiągnąć bardziej intuicyjny efekt wypisania na ekran ciągu: 0 1 2 3 4. Należy tylko zmodyfikować pierwszą pętlę:
for (int i = 0; i < 5; ++i)
{
  int j = i;
  array.Add(delegate() { Console.WriteLine(j); });
}
Dla takiego przypadku kod wygenerowany przez kompilator będzie już inny. W szczególności zostanie utworzonych 5 instancji klasy DisplayClass, a nie jedna jak w poprzednim wypadku.

Przykład omówiony przeze mnie jest bardzo prosty. W bardziej skomplikowanych przypadkach sprawa nie jest już tak prosta ale koncepcja implementacji domknięcia pozostaje taka sama.

10/12/2008

Czemu zdarzenia nie działają???

Home

Ostatnio pomogłem rozwiązać dwa problemy z "nie działającymi" zdarzeniami. Jak to najczęściej bywa, znając rozwiązanie, problem wydaje się banalnie prosty. Ponieważ jednak dojście do rozwiązania nie zawsze jest już tak proste postanowiłem napisać ten post.

Ogólnie problem został mi przedstawiony mniej więcej w taki sposób (luźny cytat): Podczepiłem się pod zdarzenia kilku kontrolek ale po wykonaniu post back'a do strony, metody obsługi zdarzeń nie są wołane.

W obu wspomnianych sytuacjach obserwowany efekt był taki sam (metody obsługi zdarzeń nie były wołane), natomiast przyczyna błędu troszeczkę inna. Błędu, ponieważ to oczywiście nie bug w maszynerii ASP.Net tylko najzwyklejszy w świecie błąd programisty.

Problem numer 1

W pierwszym przypadku programista dynamicznie tworzył kontrolkę, subskrybował jej zdarzenia, a następnie dodawał ją do strony. Dla ustalenia uwagi przyjmijmy, że była to kontrolka ListBox. Opisywany kod mógł więc wyglądać następująco:
...
ListBox lb = new ListBox();
lb.AutoPostBack = true;
lb.SelectedIndexChanged += new EventHandler(lb_SelectedIndexChanged);
lb.Items.Add("a");
lb.Items.Add("b");
lb.Items.Add("c");

this.Panel.Controls.Add(lb);
...
Sam w sobie, kod ten jest jak najbardziej poprawny. Błąd polegał na tym, że kod ten był wykonywany tylko w przypadku inicjalnego odwołania do strony. W przypadku post back'a czyli kiedy właściwość IsPostBack była równa true już nie. A skoro kontrolka nie została utworzona to zdarzenie nie mogło zostać wygenerowane.

Problem numer 2

W drugim przypadku programista używał statycznie osadzonej na stronie kontrolki DataList, zasilał ją danymi i wołał metodę DataBind. Kod strony przypominał koncepcyjnie kod poniżej:
...
this.DataList.DataSource = new string[] { "a", "b", "c" };
this.DataList.DataBind();
...
...
<asp:DataList ID="DataList" runat="server" EnableViewState="false">
   <ItemTemplate>
      <asp:Button ID="Button" runat="server" Text="<%# Container.DataItem %>" OnClick="OnClick" />
   </ItemTemplate>
</asp:DataList>
...
Podobnie jak wcześniej, strona nie działała tak jak powinna, ponieważ w przypadku post back'a nie była wołana metoda DataBind kontrolki DataList i przyciski nie były tworzone i w związku z tym zdarzenia nie mogły zostać wygenerowane. W tym jednak przypadku, pośrednią przyczyną błędu był fakt, że View State został wyłączony. Gdyby był włączony, przyciski zostałyby odtworzone na jego podstawie.

Wnioski

Konkluzja jest bardzo prosta. W przypadku dynamicznego tworzenia kontrolek, czy to bezpośrednio czy to przy okazji użycia kontrolek data bound zawsze należy pamiętać aby dynamiczne kontrolki były tworzone nie tylko przy inicjalnym odwołaniu do strony ale również przy kolejnych.

08/12/2008

Włączanie i wyłączanie kaskadowych arkuszy stylów

Home

Jeśli potrzebujemy szybki i łatwo zaimplementować włączanie i wyłączanie kaskadowych arkuszy stylów na stronie możemy zastosować taki kod:
...
<head runat="server">
    <link id="link" 
        type="text/css" 
        rel="Stylesheet"
        href="~/style.css" 
        runat="server" />
</head>
...
Teraz wystarczy sterować widocznością serwerowej kontrolki Html. Jeśli kontrolka nie będzie widoczna:
this.link.Visible = false;
to nie zostanie wzięta pod uwagę podczas renderowania strony i styl nie zostanie zastosowany do strony. Odwrotnie, jeśli kontrolka będzie widoczna:
this.link.Visible = true;
to podczas renderowania strony zostanie uwzględniona i styl zostanie zastosowany do strony. Bardzo proste ale skuteczne.

04/12/2008

Transformacje Xsl i przestrzenie nazw XML

Home

Przy używaniu transformacji Xsl należy pamiętać o przestrzeniach nazw Xml. Załóżmy, że mamy dokument Xml i transformację Xsl do jej przetwarzania:
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type='text/xsl' href='Transformation.xsl'?>
<A>
   <B>
      bbb
   </B>
   <B>
      bbb
   </B>
</A>
Transformajca wygląda natomiast tak:
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet version="1.0"  xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" indent="yes" standalone="no" omit-xml-declaration="yes" encoding="windows-1250" />

<xsl:template match="B" >
   <LI>
      <xsl:value-of select="current()"/>
   </LI>
</xsl:template>

<xsl:template match="A" >
   <HTML>
      <HEAD>
      </HEAD>
      <BODY>
         <UL>
            <xsl:apply-templates select="B"/>
         </UL>
      </BODY>
   </HTML>
</xsl:template>

</xsl:stylesheet>
Wynikiem działania przedstawionej transformacji na przykładowym dokumencie Xml powinna być lista:
  • bbb
  • bbb
Wynik będzie zupełnie inny jeśli zmodyfikujemy dokument Xml w następujący sposób:
...
<A xmlns="a.b.c">
...
Po tej zmianie otrzymamy taki, mało przyjazny rezultat transformacji:
bbb bbb
Aby rozwiązać problem należy zmodyfikować definicję transformacji poprzez jawne wskazanie przestrzeni nazw z jakiej pochodzą przetwarzane węzły dokumentu Xml. Po pierwsze należy podać definicję nowej przestrzeni nazw poprzez dodanie do węzla xsl:stylesheet atybutu xmlns:test="a.b.c". Oczywiście można podać inną skróconą nazwę przestrzeni nazw niż test. Następnie należy dodać przedrostek test: przed każdym odwołaniem do węzła z dokumentu np.:
...
<xsl:stylesheet version="1.0"  xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:test="a.b.c">
...
...
<xsl:template match="test:B" >
   <LI>
      <xsl:value-of select="current()"/>
   </LI>
</xsl:template>
...