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:
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:
- Kuigi nimed on ikka veel ObservableCollection-is, siis elementi Lotte listboxis ei ole (Lotte on lisatud pärast listBox.ItemsSource = … rida)
- 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:
Lae alla (zip)
Lõppsõna
Binable LINQ on lahe ning veel lahedam oleks, kui ta saaks päris LINQ loomulikuks osaks
.