AJAX Control Toolkit –
ListSearchExtender
Ez egy példaalkalmazáshoz tartozó leírás! Az alkalmazás erről a címről tölthető el:
http://www.devportal.hu/portal/Detailed.aspx?NewsId=c6a29adc-a3dd-4412-9b59-1b9ea45002a6 vagy
Ezen a héten a ListSearchExtender-rel ismerkedünk meg, illetve belekukkantuk egy kicsit az Ajaxcontroltoolkit szerelvénybe is.
ListSearchExtender alapok
A ListSearch egy olyan okos kis extender, mely arra szolgál, hogy egy DropDownList-ben vagy ListBox-ban megkönnyítsük a felhasználónak a megfelelő elem kiválasztását. A segítség olyan formában érkezik, hogy amint belekattint a kliens valamelyik vezérlőbe (vagy a DDL-be, vagy az LB-be), akkor megjelenik egy címke, amibe gépelhet. Utána automatikusan ahhoz az elemhez fog ugrani a vezérlő, amelyiknek az elejére ráillik az eddig begépelt pattern.
Gondolom ilyennel már mindenki találkozott, winform-os környezetben biztosan.
Az egyetlen dolog, amit ennél az extender-nél testreszabhatunk az a megjelenítendő szöveg (amiben megkérjük, hogy gépelje be a keresendő elem elejét), illetve annak kinézete, és elhelyezkedése.
ListSearchExtender használata
Most egy olyan webalkalmazást fogunk elkészíteni, mely az ajaxcontroltoolkit.dll bizonyos részeit fogja visszafejteni IL kódból, számunkra is érthető formába. Lesz egy DropDownList-ünk, melyet a szerelvényen belüli osztályok, illetve enumerátorok neveivel töltünk fel (az osztályok közül is csak azokkal, amelyek nem nested-ek, vagyis nincsen(ek) alosztálya(i)).
Ezen kívül az oldalon található lesz még egy ListBox is, amelyből majd azt lehet kiválasztani, hogy milyen típusú member-jeit (tagjait) listázzuk ki az adott osztálynak (Mehtods, Events, Propreties, Fields). Enumerátor esetén figyelmen kívül fogjuk hagyni a ListBox-ot, és mindig a lehetséges értékeket fogjuk felsorolni. Az eredményt egy label-be fogjuk kiíratni.
Ha autopostback-re állítanánk a listavezérlőket, akkor nem lenne túl sok értelme az LSE-nek, ezért egy ImageButton-t is beveszünk a buliba.
Az alkalmazásunk markup kódja most valahogy így néz ki:
<asp:DropDownList ID="DDL_Classes" runat="server">
</asp:DropDownList><br/><br/>
<asp:ListBox ID="LB_members" runat="server" Width="100px" Height="50px">
<asp:ListItem Text="Methods" Selected="True" />
<asp:ListItem Text="Events" />
<asp:ListItem Text="Properties" />
<asp:ListItem Text="Fields" />
</asp:ListBox>
<asp:ImageButton ID="IB" runat="server" ImageUrl="~/filter.gif" OnClick="reflect_act" /><br/><br /><br />
<asp:UpdatePanel ID="UP" runat="server">
<ContentTemplate>
<asp:Label ID="lbl_result" runat="server" Text=""></asp:Label>
</ContentTemplate>
<Triggers><asp:AsyncPostBackTrigger ControlID="IB" EventName="Click" /></Triggers>
</asp:UpdatePanel>
Ezek után irány a code-behind fájl! Ahhoz, hogy egy szerelvényen belüli típusokat (osztályokat) el tudjunk érni, szükségünk lesz a reflection-re (reflexióra), vagyis a System.Reflection névtérre. Ezen kívül a kódban még fogunk használni generikus listákat is, ezért a System.Collections.Gerenic namespace-t is using-oljuk.
Page_Load-ból dinamikusan töltjük fel a DDL_Classes listavezérlőnket (természetesen csak az első oldalkérésnél, hiszen postback esetén, ha újra feltöltenénk, akkor mindig az első elem lenne a kiválasztott!) Nézzük is meg a Page_Load-hoz rendelt kódot:
if (!IsPostBack)
{
act = Assembly.LoadFrom(Server.MapPath("Bin/AjaxControlToolkit.dll"));
Type[] classes = act.GetTypes();
List<string> classnames = new List<string>();
foreach (Type myclass in classes)
{
if (!myclass.IsNested && myclass.Namespace == "AjaxControlToolkit")
{
classnames.Add(myclass.Name);
}
}
classnames.Sort();
classnames.ToArray();
DDL_Classes.DataSource = classnames;
DDL_Classes.DataBind();
}
Az act egy privát Assembly típusú adattag (private Assembly act;).
A GetTypes() metódussal elkérjük a szerelvényen belüli típusokat (az enumerátor is típus!), majd utána csak azok neveit tároljuk a classnames listában, amelyek nem tartalmaznak alosztályokat, illetve az AjaxControlToolkit névtéren belül helyezkednek el. (Az AjaxControlToolkit.dll ezen kívül még két névteret tartalmaz, de azokban csak segédobjektumok vannak!). Azért foglalkozunk csak a nem nested osztályokkal, mert az alosztályokat tartalmazóaknál, a GetType() metódus (lsd. lentebb) null értéket ad vissza, így viszont eléggé nehézkes belőle kinyerni bármilyen információt is . (természetesen nem megoldhatatlan probléma ez, de most a példaalkalmazásban bőven elegendő az a 196 típus, amely megfelel a fentebb leírt elvárásoknak).
Végül pedig a kódunkban, abc szerint rendezzük a típusok neveit.
Ha ezzel megvagyunk, akkor már csak két dolog van hátra, megírni a reflect_act metódust és berántani a markup kódba két LSE-t és bekonfigolni. Folytassuk a munkánkat a code-behind írásával, íme az ImageButton click eseményéhez rendelt kód:
act = Assembly.LoadFrom(Server.MapPath("Bin/AjaxControlToolkit.dll"));
Type myclass = act.GetType(String.Format("AjaxControlToolkit.{0}",DDL_Classes.SelectedValue.ToString()));
MemberInfo[] details = null;
if (myclass.IsEnum)
{
details = myclass.GetFields();
lbl_result.Text = String.Format("<u><i>Enum – </i>{0}</u></br>", myclass.Name);
}
else
{
lbl_result.Text = String.Format("<u><i>Class/Requested Memberslist – </i><b>{0}/{1}</b></u><br><br/>", myclass.Name,LB_members.SelectedItem );
switch (LB_members.SelectedItem.Text)
{
case "Methods":
details = myclass.GetMethods();
break;
case "Events":
details = myclass.GetEvents();
break;
case "Properties":
details = myclass.GetProperties();
break;
case "Fields":
details = myclass.GetFields();
break;
}
}
foreach (MemberInfo met in details)
{
lbl_result.Text += String.Format("<b>{0}</b>-<i>{1}</i><br/>", met.DeclaringType, met.Name);
}
if (details.Length < 1)
{
lbl_result.Text += "<font color=’red’><b>No member found!</b></font>";
}
Először betöltjük az ACT szerelvényt. (Éles alkalmazás esetén érdemes ezt a Page_Load-nál Cache-ben eltárolni!).
Ezek után elkérjük azt a típust, melyet kiválasztott a felhasználó, a GetType() metódus segítéségével. Fontos, hogy ilyenkor a teljes nevét meg kell adnunk a típusnak (, vagyis névterekkel együtt)!
A Type osztálynak van egy csomó GetXXX metódusa, melyek mind egy MemberInfo tömböt adnak vissza, így létrehozunk egy ilyen típusú változót (details).
Ezek után leellenőrizzük, hogy a típus enumerátor-e, ha igen, akkor a GetFields metódus segítségével elkérjük az értékeit! Ha nem enum, akkor pedig a ListBox kiválasztott elemétől függően hívjuk meg valamelyik GetXXX metódust.
Végül pedig végigiterálunk a member-ökön, és kiíratjuk a member nevét, illetve, hogy mely osztályon belül lett deklarálva. Ha az adott osztálynak nincsen ilyen típusú tagja, akkor kiírjuk, hogy nem találtunk (mivel a details egy tömb, ezért a length tulajdonsággal ezt egyszerűen le tudjuk kérdezni).
Most, hogy már a dolog lényegi részével kész vagyunk, ideje egy kicsit fokoznunk a felhasználói élményt. Húzzunk egy LSE-t a DDL alá, illetve egy másikat a LB alá. Az elsőt az alábbi módon állítsuk be:
<ajaxToolkit:ListSearchExtender ID="LSE_1" runat="server"
TargetControlID="DDL_Classes"
PromptText="<i>Pls, type class or enum name!</i>" >
<Animations>
<OnShow>
<Sequence>
<HideAction Visible="true" />
<FadeIn duration="1" Fps="25"/>
</Sequence>
</OnShow>
</Animations>
</ajaxToolkit:ListSearchExtender>
Alapértelmezés szerint, ha a PromtText tulajdonságban nem szabunk meg valamilyen egyedi szöveget, akkor az alábbi mondat fog automatikusan megjelenni a DDL felett, amikor a kliens belekattint: Type to search. Értelemszerűen amennyiben a felhasználó leüt egy billentyűt, ez a szöveg eltűnik, és helyette megjelenik a begépelt betű. (ez viszont már nem lesz dőlt, mint a promttext, megoldást lsd. lentebb).
Ehhez az extender-hez is rendelhető ugyanúgy animáció, mint pl.: HoverMenuExtender-hez vagy DropDownExtender-hez. A különbség az, hogy nekem sehogy sem sikerült elérnem azt, hogy az OnHide eseményhez rendelt animáció legalább egyszer lefusson, mindig csak a „normál” villan egyet és eltűnik az „animáció” fogadott.
A másik ListSearch extender-ün konfig után, valahogy így nézzen ki:
<ajaxToolkit:ListSearchExtender ID="LSE_2" runat="server"
TargetControlID="LB_members"
PromptPosition="Bottom"
PromptCssClass="promt_text">
</ajaxToolkit:ListSearchExtender>
.promt_text
{
background-color: yellow;
font-style: italic;
}
A PromtPosition tulajdonságnál azt állíthatjuk be, hogy a PromtText-es címke, amelybe a kliens majd gépelhet, az adott vezérlő fölött (Top, ez az alapértelmezett), vagy alatt (Bottom) helyezkedjen el. (DDL-nél értelemszerűn érdemes fölé tenni, hiszen lefelé nyílik a legördülő-lista).
A PromtCssClass tulajdonsággal értelemszerűen a promttext, illetve a begépelt pattern kinézete formázható!
Végre elkészült az alkalmazásunk, teszteljük!
Zárásképpen, két bug:
– Ha az LB magasságát markup-ból vagy css-ből korlátozzuk, akkor néha villogni fog a vezérlő, illetve a dinamikus címke is!
– Safari esetén, ha pattern gépelés közben backspace-t üt le a felhasználó, akkor az előző oldalon fogja magát találni
Erre egy megoldást az alábbi oldalon a ListSearchExtender Know Issues résznél találhattok:
http://www.asp.net/AJAX/AjaxControlToolkit/Samples/ListSearch/ListSearch.aspx