Navigeeritav Silverlight #1

Meediarikaste, interaktiivsete ja muudmoodi udupeenete veebirakenduste, ehk siis Silverlight ja Flash puhul on tihtilugu nii, et nende seisu ei ole võimalik salvestada ja jagada. Url ei muutu ja seega ei saa saata linki sõbrale ega lisada lehte lemmikutesse. Selles postituses püüan olukorda parandada kasutades silda Silverlight rakenduse ja teda sisaldava veebilehe vahel.

HTML Bridge, mis see veel on?

HTML Bridge on mudel/sild, mis võimaldab Silverlight rakenduses kinnituda teda paigutava veebilehe sündmuste külge ja/või muuta selle lehe dokumendiobjektimudelit ehk DOM-i. Vastupidi ka, HTML sild võimaldab host-lehel JavaScriptiga Silverlight rakenduse meetodeid välja kutsuda.

Sellest, kuidas HTML lehe sündmuste külge kinnituda on siin blogis põgusalt juttu olnud (vaata: Hiire kerimisnupp).

Ja DOM-i muutmise näide on siin:

using System.Windows.Browser;

HtmlPage.Document.GetElementsByTagName("title")[0].SetProperty("text", "Tere maailm!");


Navigeerimine

Navigeerimise ehk siis url-i muutmise ning hiljem selle põhjal rakenduse seisu seadmise saavutamegi, kasutades seda silda. Kahjuks päris nii ei saa, et muudame tervet urli, näiteks www.seb.ee/kontojaak aga saab kasutada ankruid – www.minurakendus.ee#muutuja1=tere;muutuja2=maailm

Ülesanne

Rakenduseks on kolme vahelehega rakendus, mille igal lehel on n elementi. Ülesandeks on siis võimalus konkreetsele elemendile rakenduses viidata

Rakendus koosneb kahes Grid paigutushaldurist, milles esimeses asub TabControl ning teises kaks “nuppu” tsitaatide vahel navigeerimiseks. Igas TabControl-i vahelehes on tekstikast tsitaadi jaoks.

XAML

<Grid x:Name="LayoutRoot" Background="White">
      <Grid.RowDefinitions>
          <RowDefinition Height="*"/>
          <RowDefinition Height="30"/>
      </Grid.RowDefinitions>
      <basics:TabControl x:Name="tab" Width="400" Grid.Row="0">
          <basics:TabItem Header="Cicero">
              <TextBlock TextWrapping="Wrap"/>
          </basics:TabItem>
          <basics:TabItem Header="Oscar Wilde" >
              <TextBlock TextWrapping="Wrap"/>
          </basics:TabItem>
          <basics:TabItem Header="Aristoteles" >
              <TextBlock TextWrapping="Wrap"/>
          </basics:TabItem>
      </basics:TabControl>
      <Grid Grid.Row="1">
          <Grid.ColumnDefinitions>
              <ColumnDefinition Width="0.5*"/>
              <ColumnDefinition Width="0.5*"/>
          </Grid.ColumnDefinitions>
          <TextBlock x:Name="prev" Text="Eelmine" Grid.Column="0" 
                     HorizontalAlignment="Left" 
                     MouseLeftButtonDown="changeQuote"/>
          <TextBlock x:Name="next" Text="Järgmine" Grid.Column="1" 
                     HorizontalAlignment="Right" 
                     MouseLeftButtonDown="changeQuote"/>
      </Grid>
 </Grid>


URL-i uuendamine

Url-i on vaja uuendada, kui vahetatakse tsitaati või vahelehte, siis kutsutakse välja meetod changeUrl.

private void changeUrl() {
  HtmlPage.Window.NavigateToBookmark("tab=" + tab.SelectedIndex.ToString() + ";" +                                      "element=" + currentQuote);
}

See annab tulemuseks:

image thumb7 Navigeeritav Silverlight #1

Rakenduse seisu taastamine

Mina otsustasin rakenduse seisu taastada pärast seda, kui Page.xaml on edukalt laetud:

this.Loaded += new RoutedEventHandler(Page_Loaded);
void Page_Loaded(object sender, RoutedEventArgs e)
{
    LoadQuotes();    SetState();
}

Kus SetState() meetodi sisu on järgnev:

private void SetState() {
            // Kas ankur on antud? tab=1;element=17
            if (!string.IsNullOrEmpty(HtmlPage.Window.CurrentBookmark))
            {
                // jaotame url-i muutujateks, saame: tab=1 ja element=17
                string[] paarid = HtmlPage.Window.CurrentBookmark.Split(';');

                // antud on vähemalt 1 muutuja
                if (paarid.Length > 0)
                {
                    // eraldame muutuja ja tema väärtuse
                    foreach (string p in paarid)
                    {
                        string[] muutujad = p.Split('=');

                        // tab
                        if (muutujad[0].Equals("tab"))
                        {
                            // string -> int
                            currentTab = Convert.ToInt32(muutujad[1]);
                            if (currentTab <= tab.Items.Count)
                            {
                                // muudame aktiivset vahesakki
                                tab.SelectedIndex = Math.Abs(currentTab);
                            }
                        }

                        // element
                        else if (muutujad[0].Equals("element"))
                        {
                            currentQuote = Convert.ToInt32(muutujad[1]);
                        }
                    } 
                } 
            }             setTabContent();
        }


setTabContent() Meetodis seame sisuks vastava tsitaadi:

private void setTabContent() {
    TextBlock t = tab.SelectedContent as TextBlock;
    t.Text = quotes[currentQuote];
}

Siin on oluline see, et tõlgendaksime tab-i sisu kui TextBlock elementi(millel TextWrapping=Wrap), vastasel juhul kuvataks teksti ühes pikas reas.

Programm, sisaldab veel ka meetodit changeQuote(), mis kutsutakse välja, kui vajutatakse nuppude eelmine/järgmine vajutamise korral. Seal arvutatakse äärmiselt “intelligentse” algoritmi järgi, millist tsitaati näidata ning kutsutakse välja meetodid changeUrl ja setTabContent().

Lõppsõna

Põhimõtteliselt ei ole ju üldse keeruline RIA rakendusi navigeeritavaks teha, aga miskipärast ei ole see eriti levinud. See, kas üldse on vaja teha täis Silverlight rakendusi või võiks piirduda vaid pisikeste Silvelright moodulitega oma kodulehel, on juba teine jutt :)

Kood (zip)

Rakendus:

Cicero

Oscar Wilde

Aristoteles

Uut RC0-s: Nähtamatu TabControl

Hakkasin siia ühe enda ammuse programmijupi põhjal postitust kirjutama, kui avastasin, et see lihtne SL Beta 2 rakendus, mis sisaldas vaid kahte Grid paigutushaldurit, TabControl-i ja kaht tekstikasti, RC0-s tei ööta. Ühtegi veateadet tööle pannes ei tekkinud, aga osa sisu näha ei olnud.

Milles siis probleem?

TabControl on oma elukohta muutnud, enne oli tema kasutamiseks vaja lisada viide System.Windows.Controls.Extended dll-ile aga nüüd elab ta hoopis System.Windows.Controls.dll-is.

Lahendus

Seega lahenduseks on lihtsalt viidata System.Windows.Controls dll-ile ja kindlasti eemaldada viide Extended-ile.

SIlverlight 2 RC0 offline dokumentatsioon

Vähemasti allalaetav dokumentatsioon on nüüd ka RC0 jaoks olemas.

http://code.msdn.microsoft.com/Project/Download/FileDownload.aspx?ProjectName=silverlightsdk&DownloadId=3304

Seda, kas see veebi ka jõudnud on ma ei tea, sest ükskõik millist msdn.microsoft.com lehte ma ka ei külastaks, vastus on alati üks:

image thumb6 SIlverlight 2 RC0 offline dokumentatsioon

ListBoxi filtreerimine ja Bindable LINQ

Mr. Leeti sahvrist leidsin huvitava viite: Bindable LINQ. Mõtlesin, et testin ja jagan. Bindable LINQ Codeplexi lehel on kirjutatud, et tugi on olemas Silverlight 2 Beta 1 versiooni jaoks aga tegelikult töötab ka Beta 2-ga.

Ülesanne

Kujutagem ette olukorda, kus näiteks ListBoxi sisuks on mingi järjendi sisu:

<ListBox x:Name="listBox" />

List<string> nimed = new List<string>();
nimed.Add("Jaana");
nimed.Add("Indrek");
nimed.Add("Ilse");
nimed.Add("Jaan");

listBox.ItemsSource = nimed;

nimed.Add("Lotte");


See annab tulemuseks:

image thumb4 ListBoxi filtreerimine ja Bindable LINQ

Siin ei ole aga elementi Lotte, mis sai lisatud peale listBoxi sidumist nimede järjendiga.

Üks lahendus oleks igal korral, kui andmestikku muudame, listBox ära nullida:

nimed.Add("Lotte");
listBox.ItemsSource = null;
listBox.ItemsSource = nimed;


Suhteliselt ebamugav aga ajab asja ära – nüüd jõuab Lotte ka nimekirja.

On olemas aga palju lihtsam viis – kasutada tavalise Listi asemel ObservableCollection-it, mis annab oma muudatustest automaatselt teada kõigile, kellega ta seotud on. Hetke näites, uueneb automaatselt listBoxi sisu. Ainus vajalik muudatus eelnevates koodilõikudes (ListBoxi nullima enam ei pea):

ObservableCollection<string> nimed = new ObservableCollection<string>();


Samm edasi


Aga olgu meil nüüd nii, et ListBoxis on ainult osad nimed, näiteks sellised, mis vastavad mingile kindlale LINQ lausele.

Lisame näitele TextBoxi, mille abil filtreerime nimekirjast teatud nimed välja:

<TextBox x:Name="_filterTextBox"/>

ListBoxi andmeallikaks saab nüüd järgnev LINQ päring:

listBox.ItemsSource = from nimi in nimed
                      where nimi.StartsWith(_filterTextBox.Text)
                      select nimi;


Mõte on ju hea aga see ei tööta. Tekib kaks probleemi:

  1. Kuigi nimed on ikka veel ObservableCollection-is, siis elementi Lotte listboxis ei ole (Lotte on lisatud pärast listBox.ItemsSource = … rida)
  2. Kui midagi tekstikasti trükkida, siis ei toimu filtreerimist.


Üks lahendus:


Jätta esialgu ikkagi listBox.ItemsSource = nimed ning lisada tekstikastile sündmusekuular:

<TextBox x:Name="_filterTextBox" TextChanged="_filterTextBox_TextChanged"/>


Ja iga kord, kui see sündmus tekib muuta listBoxi andmeallikat:

private void _filterTextBox_TextChanged(object sender, TextChangedEventArgs e){    listBox.ItemsSource = from nimi in nimed                          where nimi.StartsWith(_filterTextBox.Text)
                          select nimi;}

Töötab, aga tegelikult on üks lahendus veel …

Bindable LINQ



Bindable LINQ is a set of extensions to LINQ that add data binding and change propagation capabilities to standard LINQ queries.

Ehk BIndable LINQ teeb LINQ päringud muudatustetundlikuks, ta saab aru, kui LINQ lause sõltub mingitest kolmandatest muutuvatest parameetritest. Siinses näites siis faktid, et nimede järjend võib muutuda ja et tekstikastist tulev tekst muutub. Bindable LINQ võtab kõike seda arvesse ning lahendab meie ülesande elegantselt:

listBox.ItemsSource = from nimi in nimed.AsBindable()
                      where nimi.StartsWith(FilterTextBox.Text)
                      select nimi;

See AsBindable() tulebki BinableLinq-st, mille saab tõmmata siit. Kasutamiseks tuleb see lisada projekti: Project->Add Reference->Browse sinna, kus on BindableLINQ.dll, samuti tuleb lisada using Bindable.Linq;

Kogu kood:


Page.xaml

<UserControl x:Class="Bindable.Page"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Width="400" Height="300">
    <Grid x:Name="LayoutRoot" Background="White">
        <Grid.RowDefinitions>
            <RowDefinition Height="40"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
            <TextBox x:Name="_filterTextBox" Grid.Row="0" />
            <ListBox x:Name="listBox" Grid.Row="1"/>
    </Grid>
</UserControl>


Page.xaml.cs

using System.Collections.ObjectModel;
using System.Windows.Controls;
using Bindable.Linq;

namespace Bindable
{
    public partial class Page : UserControl
    {
        private ObservableCollection<string> nimed;

        public Page()
        {
            InitializeComponent();
            Loaded += new System.Windows.RoutedEventHandler(Page_Loaded);
        }

        void Page_Loaded(object sender, System.Windows.RoutedEventArgs e)
        {
            nimed = new ObservableCollection<string>();
            nimed.Add("Jaana");
            nimed.Add("Indrek");
            nimed.Add("Ilse");
            nimed.Add("Jaan");

            listBox.ItemsSource = from nimi in nimed.AsBindable()
                                  where nimi.StartsWith(FilterTextBox.Text)
                                  select nimi;

            nimed.Add("Lotte");
        }

        public TextBox FilterTextBox
        {
            get { return _filterTextBox; }
        }
    }
}


Tähele tuleks panna kõige viimast meetodit – nimelt peab _filterTextBoxi avalikuks tegema, muidu Bindable LINQ tema väärtust kätte ei saa.

Tulemus:

image thumb5 ListBoxi filtreerimine ja Bindable LINQ

Testi (Silverlight 2 RC 0)

Lae alla (zip)

Lõppsõna

Binable LINQ on lahe ning veel lahedam oleks, kui ta saaks päris LINQ loomulikuks osaks :) .

Silverlight tugi nüüd Visual Web Developers Expressis

Nüüd saab Silverlight rakendusi arendada ka kasutades kõigile täitsa tasuta saadaval olevat Visual Web Developers Expressi.

Kõigepealt on vaja Visual Web Developer 2008 SP1 versiooni:

http://www.microsoft.com/express/download/#webInstall

ja siis

RC0 Silverlight Tools for Visual Studio 2008 SP1 (RC0)

http://www.microsoft.com/downloads/details.aspx?FamilyId=c22d6a7b-546f-4407-8ef6-d60c8ee221ed&displaylang=en

ja võib tööle hakata :)

Uut RC0-s: ComboBox ja PasswordBox

Kuidas on võimalik veebitehnoloogial eksisteerida, kui ta ei sisalda comboboxi ja paroolivälja? Õige, ei olegi ja seda mõistis Silverlighti meeskond ka ja enne toote turule paiskamist parandasid oma vea :)

PasswordBox või paroolikast töötab nii nagu peab – sisestad tähti aga näha on hoopis mingid sümbolid (PasswordChar), milleks vaikimisi on ?. Tore on, et olemas on ka MaxLength atribuut, millega piirata sisestavate paroolide pikkuseid.

<PasswordBox x:Name="parool" MaxLength="8" PasswordChar="?" />

ComboBox on samuti täiesti tüüpiline, ta võib sisaldada arvu elemente (ComboBoxItem), mis ei pea tingimata olema sõned. Kui valik muutub, siis antakse sellest (SelectedItem) teada sündmuse SelectionChanged kaudu. Hetkel ma ei suutnud välja mõelda, kuidas muuta vaikimisi valitud elementi … ma usun, et see on võimalik aga kuidas … Kui tekib dokumentatsioon, siis ilmselt saab selgust :)

<ComboBox x:Name="comboBox" SelectionChanged="comboBox_SelectionChanged">
    <ComboBoxItem Content="?" />
    <ComboBoxItem Content="?"/>
    <ComboBoxItem Content="?"/>
</ComboBox>

Testi lihtsat näidisprojekti (SLS)
 
Lae alla (zip):

Uut RC0-s: MessageBox

Mugav viis veebirakenduste silumiseks on kasutada alert aknaid, sest nende tegemine on lihtne ning nad ei risusta koodi. Kui varem oli alert akna tegemiseks vaja silda host-lehega, siis nüüd on see palju lihtsam.

C#

MessageBox.Show("Teade");

image thumb1 Uut RC0 s: MessageBox

MessageBox.Show("Teade", "Teate pealkiri", MessageBoxButton.OKCancel);

image thumb2 Uut RC0 s: MessageBox

Ja kui tarvis, siis miks ka mitte kasutada MessageBox-i kasutajaliideses aga siis on tõenäoliselt vaja püüda kinni ka kasutaja vastus:

MessageBoxResult vastus =  MessageBox.Show("Teade", "Teate pealkiri", MessageBoxButton.OKCancel);
if (vastus == MessageBoxResult.OK) {
    MessageBox.Show("Vajutasid OK ");
}
else
{
    MessageBox.Show("Vajustasid Cancel");
}

Siin on lihtne näide:

http://silverlight.services.live.com/invoke/60908/MessageBoxTest/iframe.html