Master-details with Entity Framework in WPF by using MVVM pattern


In this short blog post we will present how to implement WPF application with Master-detail relation in Entity Framework by using MVVM pattern. We will start with creating the new Visual Studio 2010 WPF Application project, and add reference to MVVM Light Toolkit library, which you can find on http://mvvmlight.codeplex.com/.  The picture below shows the starting WPF project:

Now that we have starting project, we can add the new ADO.NET Entity Framework model with NorthWind SQL database. Map the only two tables Order and OrderDetails, after that we have the following situation. If you not familiar with this procedure, more details you can find on my previous post.

The next step in implementation will be creating the ModelView of the MainWindow. The ModelView will contain implementation of all logic of the Master-detail relation. To do that point with right-click on the project, select Add-Class of the context menu item and enter the name MainWindowViewModel. If you have full instatlation of the MVVM Light Toolkit you can use MVVM template specialized for creating ModelView class.

After we create file of the MainWindowViewModel, we have to implement class and add two properties MasterCollection, and DetailsCollection.

The Next source code listing shows implementation of the MainWindowViewModel. This is minimal code for work with master – details in Entity Framework.

using GalaSoft.MvvmLight;
using System.Windows.Data;

namespace WPF_EF_MasterDetailsDemo
{
   public class MainWindowViewModel:ViewModelBase
   {
      NorthwindEntities ctx = new NorthwindEntities();
      public MainWindowViewModel()
     {

     }
     private CollectionViewSource _entityMasterView;
     public CollectionViewSource EntityMasterView
     {
       get
       {
        if (_entityMasterView == null)
          GetMasterEntityCollection();
        return _entityMasterView;
       }
    }
    public CollectionViewSource _entityDetailsView;
    public CollectionViewSource EntityDetailsView
    {
     get
     {
      if (_entityDetailsView == null)
           GetMasterEntityCollection();
       return _entityDetailsView;
     }
   }
   private void GetMasterEntityCollection()
   {
       _entityDetailsView = new CollectionViewSource();
       _entityMasterView = new CollectionViewSource();
       _entityMasterView.Source = ctx.Orders;
       _entityMasterView.View.CurrentChanged += (x, y) =>
         {
           _entityDetailsView.Source =((Orders)_entityMasterView.View.CurrentItem).Order_Details;
         };
       _entityMasterView.View.Refresh();
   }
   }
}

After we implemented ViewModel, we have to put some GUI stuff on the MainWindow.xaml, so open the MainWindow.xaml, put two labels and two DataGrid controls in to Main Window. This looks line on the following picture:

The sample is so short that it can fit in to single image ;).

In the code-behind constructor create MainWindowViewModel and assign to Windows DataContext, and that’s it.

This post demonstrate one posible  implementation of the Master-detail relation in EF by using the MVVM pattern. The source code of this project you can find on SkyDrive.

Visual Studio 2010 Beta 2 i WPF dizajner -Kratak pregled


Uvod

Kako je i najavljeno VS 2010 Beta 2, dostupna je od 21.10. 2009. Na oficijelnoj stranici Visual Studia nalazi se link za instalaciju. Međutim, radi se o Bootstrapperu online instalaciji, koja kešira nekoliko mb, na vaš disk, a zatim po pokretanju simultano downloada i instalira komponente aplikacije. I ovom postu napravićemo jedan mali tutorijal oko instalacije VS 2010 Beta 2, kao i prezentirati neke novine kod razvoja WPF aplikacija. Iako instalacija je dosta trivijalna potrebno je podsjetiti koji su preduslovi instalacije ovog proizvoda.

  • Da bi instalirali VS 2010 potrebno je deinstalirati prethodnu verziju Beta 1 u koliko je instalirana.
  • Microsoftova je preporuka da se beta verzije ne instaliraju na produkcijske mašine.

U koliko želite da pogledate video prezentaciju instalacije VS 2010 i TFS 2010 možete je naći na Channel9 stranici.

Važno je napomenuti da VS 2010 u beta 2 dolazi u sljedećim SKU verzijama:

  • Visual Studio Express
  • Visual Studio 2010 Professional
  • Visual Studio 2010 Premium
  • Visual Studio 2010 Ultimate
    Šta koja od verzija predstavlja i sadržava možete pogledati na MSDN stranici. Sjetimo se da su prethodne verzije 2005 i 2008 dolazile u: Standard, Professional i Team Suite verzijama. VS 2010 može biti instaliran na istoj mašini na kojoj se nalazi VS 2008, i to je novina u ovoj verziji, tako da neće uticati jedan na drugog.

Ako ste deinstalirali Beta 1 verziju, otvorite oficijelnu stranicu VS 2010.

Instalacija VS 2010 Ultimate

Pokrenite IE i pristupite stranici http://www.microsoft.com/visualstudio/en-us/try/default.mspx. Na donjoj slici prikazana je početna stranica na kojoj se nalazi link za instalaciju VS 2010 beta2. Kliknite na Dugme „Download the beta now“ (Kao na slici dolje.)

image

Nakon nekoliko trenutaka pojaviće se dijalog za download. Potrvdite download i kliknite na Dugme „Run“.

image

Po završetku preuzimanja Bootstrapper datoteke pokreće se instalacija, čiji prvi prozor daje do znanja novog preuređenog i za ovakvu instalaciju podešenog instalacijskog programa.

image

Po učitavanju potrebih komponenti za pokretanje instalacije, pojavljuje se dijalog za prihvaćanje licencnih uslova, te načina instalacije.

image

Nakon klika na dugme Next pojavljuje se dijalog sa komponentima za instalaciju.

image

Za ovaj slučaj instaliraćemo samo C# i F# obzirom da su mi ti jezici interesantni za ovu beta. Naravno možete odabrati sve ostale komponente.

Nakon klika Next, počinje dugotrajni proces skidanja i instalacije Visual Studio 2010.

image

Nakon nekoliko sati pojavljuje se dijalog za instaliranim proizvodom.

image

Pokretanje VS 2010 i prvi projekat

Kada pokrenemo VS2010 dobijamo startni izgled programa. Možemo primjetiti da je startni screen preuređen u odnosu na Beta1 verziju.

image

Pokrenimo Start->New Project i formirajmo novu WPF aplikaciju.

image

U dijalogu za formiranje novog projekta primjetićemo multitargeting od 2.0,3.0, 3.5, 4.0 te novi link za podešavanje za ostale verzije .NET Frameworka npr. 3.5 SP1 i buduće verzije.

image

Kada govorimo o C# projektima, oni su ostali ne promjenjeni iz verzije 2008 .

Odaberimo WPF Application i kliknimo na dugme OK. Na samom startu primjetićemo brži rad u odnosu na Beta 1 verziju.

IntelliSense

IntelliSense prozor je dodatno uređen sa manjim borderom i ne može se resajzirati kao ni u prošloj verziji.

image

Markup IntelliSense pojavio u prethodnoj beta 1 verziji ali ga je važno spomenuti da je jedan od najčešće spominjanih zahtjeva korisnika.

image

Kod IntelliSense i Markup proširenja prilikom ukucavanje prve velike zagrade nije potrebno tipkati drugu jer Intelisense to radi automatski. U prvih nekoliko pokušaja potrebno se navići.

PropertyWindow

Ovaj prozor je i u Beta1 doživio znatna poboljšanja, a sve u svrhu olakšavanja razvoja WPF aplikacija. PropertyWindow podjeljen na Tab kartice za Properties i Events, dok to u prethodnim verzijama je bio u obliku trake.

PropertyWinodw preuređen i umjesto dugmadi za Properties i Events sada stoje tab kartice:

image image

Pored, alfabetnog sortiranja image , te prikaza osobina po kategorijamaimage, uveden je novi prikaz po Izvoru osobine image (Property Source). Donja slika pokazuje ovaj način prikaza:

image

Pored InteliSense-a za Markup proširenja, jedan od najpopularnijih novina je i PropertyMarkers dugme koje se nalazi izmedju Osobine i Vrijednosti u Propertygridu, a označava odakle vrijednost osobine potiče, a iste su označene različitim sličicama.

image

Klikom na PropertyMarkers dobijamo iskačići meni za postavljanje vrijednosti:

1. Reste Value – postavljanje podrazumijevane vrijednosti (Default Value)

2. Apply DataBinding … – Postavljenje vrijednosti preko DataBinding

3. Apply Resource.. – postavljanje vrijednosti iz resursa

Kada odaberemo drugu opciju menia dobijamo popup kontrolu sa nekoliko kategorija za povezivanje kontrole izvorom podataka:

1. Source: Ova stavka prikazije sve dostune izvore podataka u dotičnoj kontroli. Na sljedećoj slici prikazana je stavka Source, koja u prvoj koloni prikazuje dostupne resurse u ovom slučaju samo Windows.Resource, dok druga kolona prikazuje valide izvore (kolekcije) za dati resurs. Treća kolona služi prilikom odabire dodathim stavki za izvor podataka.

image

2. Path Stavka obezbjeđuje odabir putanje za izvor podataka.

image

3. Converters:Preko ove stave moguće je postaviti pretvarače, koji se nalaze kao standardni ili eksterni pretvaraći preko reference.

image

4. Options: Predstavlja razne vrijante prilikom povezivanja: Format, Binding Mode, …

image

Sa prikazanim dizajnerom za Binding praktično je moguće napraviti sve moguće načine povezivanja sa svim varijantama.

Bilo da smo za neku osobinu pridrižili vrijednost lokalnu, iz resursa ili preko Binding dizajnera,  sličica u Propertymarkeru prikazaće se različito. Donja slika prikazuje varijante pridruživanja vrijednosti osobinama.

image

Detaljan pregled Dizajnera za WPF i SL u VS 2010 možete pronaći na ovom linku.

Povezivanje Aplikacije sa bazom podataka preko ADO.NET EF

Ovom verzijom nastoji se produktivnost u razvoju WPF aplikacija dovesti na nivo Windows Forms-a, a to znači poboljšati WPF dizajnere. Pogledajmo na koji način i sa koliko vremena je potrebno naš primjer povezati sa bazom podataka i prikazati Master-Detail relaciju u WPF. Takav scenario u Windows Formsima smo poprilično brzo uspijevali napraviti zahvaljujući moćnim dizajnerima.

Dodajmo novu stavku u našem projektu, te izaberimo ADO .NET Entity Data Model. Pojavljuje nam se čarobnjak za povezivanje sa bazom podataka. Povezaćemo klasičnu NorthWind bazu sa SQL 2005 ili 2008 Servera.

image

Odaberimo dvije tabele Orders i Order_Details, te završimo sa čarobnjakom.

Iz menia Data, odaberimo Show Data Sources da se pokaže prozor DataSources, te identično kao i u Windows Formsima odvucimo Orders, pa zatim Order_Details, u sadržaj Windows1, kao na slici.

image

Pritisnimo F5 za Build projekta i bez i jedne linije koda imamo MasterDetail formu, potpuno isti način kao što to radimo u Windows Formsima.

image

Pomjeranjem retka u Orders tabeli, dobijamo Order detalje u tabeli ispod. Jedina razlika u generiranju koda između WindowsForms i WPF je što nemamo navigacijske trake za manipulaciju sa podacima (edit, delete, save), međutim to i nije nekakav nedostatak.

Prilikom izrade ovog primjera koristili smo DataGrid WPF grid kontrolu, proistekla iz WPFToolKit open source projekta na www.codeplex.com, koja je sada standardna WPF kontrola.

WPF Custom ComboBox članak na CodeProject


WpfCustomComboBox1 WpfCustomComboBox2

Zadnjih dana radio sam na kastomizaciji osnovnih windows kontrola u WPF. Jedna od interesantnih kontrola koju najviše koristim i koja ima puno prednosti u odnosu na običnu combobox kontrolu je combobox sa Grid iskačućom kontrolom. Jutros je članak koji sam napisao za www.codeproject.com, objavljen. Članak možete pogledati i naravno glasati za njega na  http://www.codeproject.com/KB/WPF/WPFCustomComboBox.aspx.

Inače na codeproject sam objavio ukupno 6 članaka, i jedan sam od prvih članova ove zajednice (redni broj 657 od preko 6 miliona članova koje broji).

Moj profil možete pogledati na http://www.codeproject.com/Members/Bahrudin-Hrnjica.

Remake: Broj u text sa C#


U nekom od prethodnih postova pisao sam o malom MFC demo programu u kojem sam implementirao jednostavnu klasu koja decimalni broj zapisuje slovima. Ovaj demo proizašao je iz čiste potrebe da u svojim komercijalnim aplikacijama implementiram ovo svojstvo. Na isti način pojavila se potreba da se ova klasa portuje i na .NET jezik C#. Verzija radi na bosanskom jeziku, a moguće je selekcijom na checkbox prikazati i hrvatsku veziju tekstualnog broja.

WPF aplikacija

Demo aplikacija slična je prethodnoj gdje se uz osnovna podešavanja naziva valute i 100-tog dijela iste, odabirajuću varijantu bosanskog ili hrvatskog jezika, unosom broja te aktivacijom dugmeta pojavljuje textualni prikaz. Na ovom linku možete pronaći C# primjer sa source kodom.

WPF i LINQ to SQL III dio, data validation


U prethodnom postu vidjeli smo na koji način možemo manipulirati podacima u smislu sortiranja, grupiranja, te filtriranja podataka. Za ovaj put prezentiraćemo validiranje podataka u WPF odnosno DataValidation. U WPF postoji predefinisan model validiranja podataka tako da svaki tip izveden iz IDataErrorInfo interfejsa podržava validiranje podataka.

Implementacija validacije podataka

Početak ovog posta baziran je na prethodnom primjeru, a potrebno je implementirari validiranje podataka za naš model podataka. Valdaciju podataka moguće je implementirati na različitim konceptialnim modelima aplikacije. U ovom poglavlju validaciju ćemo implementirati na nivou našeg modela baze podataka. Validacija će se sastojati u tome da kad god unesemo datum veći od 1. Januara 2009 godine u datum narudžbe informacija o nepravilnom unosu upozoravaće nas prilikom svakog gubljenja fokusa date kontrole na našem interfejsu. Obzirom da smo validaciju pretpostavili na nivou modela, potrebno je klasu Orders derivirati iz interfejsa IdataErrorInfo da bi iskoristili ugrađeni mehanizam u WPF.

Otvorimo datoteku NorthWindKlase.cs te definišimo parcijanu deklaraciju klase Orders na sljedeći način:

partial class Order : IDataErrorInfo
{
#region IDataErrorInfo Members
 public string Error
  {
   get { return null; }
  }
 public string this[string columnName]
  {
   get
    {
      if (columnName == "OrderDate")
         if (OrderDate > newDateTime(2009, 1, 1))
           return "Datum nije validan. Molimo ispravite datum.";
    return null;
   }
  }
#endregion
}

Klasa Order izvedena je iz dva interfejsa koja su definisana u dizajnerskog datoteci. U cs datoteci ovu klasu izvodimo iz još jednog interfejsa IdataErrorInfo. Ovaj interfejs obezbjedjuje implementaciju validacije podataka u WPF. Dvije implementacije je potrebno izvršiti i to za Property Error koja u ovom slučaju nije potrebna, a inače služi kada upravljamo sa više pogrešaka koje se mogu pojaviti u klasi Orders. Druga metoda je indexer u kojem prvo provjeravamo da li je vrijednost indexera posmatrani naziv kolone OrderDate. Zatim tekuću vrijednost provjeravamo, i ako vrijednost ne zadovoljava naše definisane kriterije vraćamo neprazan string koji u biti sadržava poruku pogreške.

Napomena: Kada u polju datuma ne postoji nikakva vrijednost (prazan string) ili vrijednost koja ne predstavlja datum, validacija takvih podataka je već implementirana tako da se takvi podaci neposmatraju ovom implementacijom. Naša validacija OrderDate kolone se sastoji u tome da ako smo definsiali datum koji je veći od 1. Januara 2009 godine, izbacuje se pogrešaka. Kada smo implementirali logiku validacije podataka u bussiness layeru, logiku je potrebno povezati sa UI kontrolama, odnosno sa TextBox kontrolom koja prikazuje OrderDate. Kada želimo da povežemo validaciju sa bilo kojom UI kontrlom postavljamo Property ValidatesOnDataErrors=True. Sa ovim je cijela implementacija validacije podataka iz bussiness layer povezana sa UI kontrolom.

Kada u ovoj fazi implementacije pokrenemo naš primjer i kad u polje OrderDate unesemo datum koji je veći od datuma 1. Januar 2009 godine, mehanizam validacije podataka se pokreće i dobijamo sljedeći sliku.

image_020720093C88EA80

Sa slike vidimo da je tekstualna kontrola Order Date promjenila boju ivice na crvenu. Ovo je standardni šablon koji se koristi kada se pokrene mehanizam validacije. Medjutim, sa crvenom bojom ivice mi ništa više ne znamo od pogreške. Za ovaj primjer implementirat ćemo šablon koji će tekst pogreške koji je uhvaćen u bussiness layeru prikazati kao tooltip naše kontrole. Da bi ovo implementirali potrebmo je definisati Stil validacije podataka. Stil validacije podataka definišemo na sljedeći način:

<!--Šablon validacije podataka-->
<Style  TargetType="TextBox">
    <Style.Triggers>
        <Trigger Property="Validation.HasError" Value="True">
            <Setter Property="ToolTip"
                    Value="{Binding RelativeSource={x:Static RelativeSource.Self},
                    Path=(Validation.Errors)[0].ErrorContent}"/>
        </Trigger>
    </Style.Triggers>
</Style>

U biti gornja implementacija je implementacija stila za TextBox kontrolu koja u sebi sadrži implementaciju Triger (okidača) kad postoji pogreška u validaciji podataka. Kada se desi tada vrijednost ToolTipa date kontrole ima vrijednost teksta pogreške.

Sada kad pokrenemo aplikaciju te unesemo pogrešan datum kao u prethodnom primjeru, te odnesemo miša do date kontrole, pojavljuje se tooltip sa tekstom pogreške.

clip_02072009image002_6B0ECD95

Kada smo definisali Stil za validaciju podataka za textbox kontrolu nismo ga definisali sa ključnom nazivom (x:Key=”stilValidacije), što onda znači da dati stil automatski podešava sve textbox kontrole.

Sa ovim smo završili tutorijal o validaciji podataka.Izvorni kod za ovaj primjer možete skinuti sa ovog linka.

WPF i LINQ to SQL II dio- grouping, filtering, sorting


U prethodnom postu smo vidjeli jedan od načina povezivanja WPF aplikacije sa bazom podataka preko LINQ to SQL. Naše putovanje ide dalje, a ovaj put obradićemo grupiranje, filtriranje i sortiranje. Tutorijal startamo tamo gdje smo stali u prethodnom postu.

Grupiranje podataka

Za razliku od WindowsFormsa u WPF je moguće na vrlo jednostavan način implementirati grupiranje podataka u listbox kontroli. Obzirom da naša listbox kontrola prikazuje samo jednu stavku potrebno ju je kastomizirati. Da bi prikazali više stavki (više kolona i tabele) u listbox kontroli potrebno je implementirati šablon podataka odnosno DataTemplate. Data template nije ništa drugo nego struktura u kojoj je definisano kako treba da izgleda pojedinačna stavka u listi. Kako bi implementirali grupiranje potrebno je, takodjer definisati šablon grupiranja podataka.

U listbox kontroli definisaćemo DataTemplate koji će prikazati DateOrder i ShipPostalCode, a grupiranje ćemo definisati po DateOrder. DataTemplate prikazan je na sljedećoj slici:

<DataTemplate x:Key="sablonPodataka">
    <StackPanel Orientation="Vertical">
        <TextBlock Text="{Binding Path=OrderDate}"/>
        <TextBlock Text="{Binding Path=ShipPostalCode}"/>
    </StackPanel>
</DataTemplate>

Gornji listing prikazuje StackPanel u kojem se vertikalno poravnavaju dvije tektualne kontrole koje su povezane sa kolonama OrderDate i ShipPostalCode.

DataTemplate grupirane liste definisan je sljedećim DataTemplateom

<DataTemplate x:Key="sablonGrupiranihPodataka">
    <StackPanel Orientation="Vertical">
        <TextBlock Text="{Binding Path=Name}" FontWeight="Bold" Foreground="Red"/>
    </StackPanel>
</DataTemplate>

Ovdje je nešto drugačija situacija. Zaglavlje grupiranja sastoji se samo od jedne tekstualne kontrole ali ova kontrola je povezana sa nazivom osobine (PropertyName) objekta PropertyGroupDescription klase. Ova dva šablona definisali smo u resursu Windowsa, da bi bili dostupni u kontrolama koje su definisane. Definisane šablone potrebno je još povezati sa listbox kontrolom. Obzirom da listbox kontrola po defaultu prikazuje samo jednu stavku i to onu koja je definisana preko DisplayMemberPath osobine, potrebno je ovu osobinu zamjeniti sa novom ItemTemplate koja ima referencu na DataTemplate koji smo definisali na samom početku.

Pored toga, listbox kontroli potrebo je definisati Stil grupiranja koji će imati referencu na drugi šablon kojeg smo formirali. Sve rečeno prikazano je na listingu:

<ListBox Name="listBox1" Grid.RowSpan="6" Grid.Row="3"
         IsSynchronizedWithCurrentItem="True"
         ItemsSource="{Binding Source={StaticResource listaNarudzbi}}"
         ItemTemplate="{StaticResource sablonPodataka}" Grid.ColumnSpan="2">
    <ListBox.GroupStyle>
        <GroupStyle HeaderTemplate="{StaticResource sablonGrupiranihPodataka}" />
    </ListBox.GroupStyle>
</ListBox>

Kada smo definisali prethodne korake naša aplikacija je spremna za grupiranje podataka. Sada je još potrebno implementirati logiku odnosno opciju za grupiranje podataka. U tom smislu definisaćemo Checkbox dugme kojim ćemo grupirati odnosno degrupirati podatke u listbox kontroli. CheckBox dugme postavićemo kao na slici dolje, te mu implementirati dogadjaje Checked i UnChecked .

image

Implementacija dogadjaja sastoji se u formiranju PropertyGroupDescription objekta i postavljanjem Naziva grupirane kolone OrderDate. Zatim novoformirani objekat pridodajemo u kolekciju grupiranja koja je definisana u defaultView objektu.

Kada želimo grupiranje ukloniti potrebno je iz kolekcije izbaciti objekt za grupiranje. Implementacija se nalazi na sljedećem listingu.

//Grupiranje podataka
private void button3_Checked(object sender, RoutedEventArgs e)
{
    PropertyGroupDescription groupDesc = new PropertyGroupDescription("OrderDate");
    defaultView.GroupDescriptions.Add(groupDesc);
}
//De grupiranje podataka
private void button3_Unchecked(object sender, RoutedEventArgs e)
{
    defaultView.GroupDescriptions.Clear();
}

Pokrenimo aplikaciju te isprobajmo kako grupiranje radi.

Filtriranje podataka

Sljedeća implementacija sastoji se u filtriranju podataka. Filtriranje podataka postižemo definišući osobinu (Property) Filter objekta defaultView. Za ovu implementaciju definisaćemo edit kontrolu koja će filtrirati narudžbe po poštanskom broju (ShiPostalCode). Prilikom kucanja poštanskog broja stavke u listbox kontroli će se filtrirati na osnovu unešenog broja. Edit kontrola prikazana ja na sljedećoj slici:

image

Implementacija filtriranja stavki po poštanskom broju prikazan je na sljedećem listingu:

//Filtriranje podataka u listi
private void textBox6_TextChanged(object sender, TextChangedEventArgs e)
{
    string text = this.textBox6.Text.ToLowerInvariant();
    if (defaultView.CanFilter)
        defaultView.Filter = delegate(object obj)
        {
            Order narudzba = obj as Order;

            if (narudzba != null && narudzba.ShipPostalCode != null)
                return narudzba.ShipPostalCode.ToLowerInvariant().Contains(text);
            return false;
        };
}

Filter smo definisanli delagatom koji za parametar uzima narudžbu iz liste, te provjerava da li sadrži definisani text, te vraća true ako postoji traženi text, inače vraća false.

Sortiranje podataka

Slično kao i kod grupiranja sortiranje definišemo preko objekta ListCollectionView podešavanje propertya SortDescriptions. Sljedeći listing prikazuje sortiranje po datumu te u drugoj metodi uklanjamo sortiranje definisano prethodno.

//Sortiranje po datumu
private void checkBox1_Checked(object sender, RoutedEventArgs e)
{
   SortDescription sortiranjePoDatumu=new SortDescription("OrderDate",ListSortDirection.Ascending);
   defaultView.SortDescriptions.Add(sortiranjePoDatumu);
}
//Resetovanje sortiranje na početno
private void checkBox1_Unchecked(object sender, RoutedEventArgs e)
{
    defaultView.SortDescriptions.Clear();
}

Na sljedećoj slici prikazan je checkbox preko kojeg definišemo sortiranje podataka, Kada otkačimo checkbox sortiranje se resetuje.

image

Ovim smo završili našu implementcaiju grupiranja i sortiranja podatka. Izvorni kod za ovaj primjer dat je na ovom linku.

WPF i LINQ to SQL


U narednih nekoliko postova biće riječi o novoj microsoftovoj tehnologiji za razvoj windows aplikacija Windows Presentation Foundation, kao i razvoj aplikacija koje se oslanjaju na baze podataka preko LINQ to SQL i LINQ to Entity. Biće prikazani osnovni modeli aplikacije sa prikazom implementacije osnovnih CRUD operacija razvijani na WPF tehologiji.

Kratki uvod

WPF je nova tehnologija razvoja windows aplikacija bazira na DirectX grafičkoj biblioteci, umjesto klasičnih GDI aplikacija na kojim su se zasnivale sve dosadašnje microsoftove tehnologije. Ovim prelazom omogućen je razvoj windows grafičkih interfejsa na sličan način kako se to radi sa web aplikacijama. U tom smislu razvijen je novi programski jezik XAML (čitaj zaml) koji je sastavni dio .NET Frameworka. Sa razvojem XAML razvijen je novi skup programskih rješenja koja pomažu u dizajnu windows aplikacija nazvan Expression Studio. Ovaj paket aplikacija uključuje Expression Blend, Expresion Design i druge aplikacije kojim razvijamo GUI dok logiku ispod kontrola GUI-a razvijamo u klasičnim razvojnim alatima poput Visual Studia. Sa ovim se razdvojio dizaj i logika aplikacije te uvela nova tehnologija razvoja aplikacija kao i sistematičnija kolaboracija izmedju dizajnera i developera.

Informacije o WPF mogu se pronaći na internetu sa vrlo različitim novoima predznanja, te u ovom vrlo kratkom uvodu želio sam samo natuknuti neke od osnovnih značajki ove tehnologije. Svakako prije čitanja ovih postova potrebno je minimalno predznanje o WPF tehnologiji. Prvi post će biti prikazan u obliku tutorijala s kojim ćemo implementirati jednostavan primjer povezivanja baze podataka sa WPF aplikacijom, te upoznati osnovne operacije umetanja, modifikacije i brisanja zapisa iz baze podataka. Primjer će također prikazati neke od metoda na koji način koristimo Visual Studio 2008 i Microsoft Expression Blend u razvoju aplikcija. Za ovaj i primjere koji će biti prikazani u narednim postovima, potrebna je instalacija Visual Studio 2008 SP1, Expresion Blend 2, Microsoft SQL Server 2005 ili 2008,NorthWind baza podataka, a svi se oni mogu skinuti na microsoftovim stranicama, kao i u besplatin express verzijama.

NorthWind WPF primjer

Prije početka potrebno je NorthWind bazu podataka instalirati na server, kako bi se kroz Visual studio mogao formirati model baze podataka.

Pokrenimo VS 2008 te formirajmo novi WPF projekat kao na sljedećoj slici.

Nakon formiranja WPF projekta potrebno je formirati LINtoSQL model northwind baze podataka. Način na koji formiramo model možete pogledati na jednom od mojih prethodnih blog postova. Ovaj put naš model se sastoji od sljedećih tabela baze podataka: Customer, Employee, Order, Order_Detail. Sada naš projekat izgleda kao na slici 2.

U solucijskog exploreru možemo vidjeti da se naš projekat sastoji od Window1.xaml i korespodentne cs datoteke, App.xaml te korespodentne cs datoteke, kao i novoformiranog modela baze podataka kojeg smo objašnjavali u prethodnim postovima. Da bi NorthWind model baze podataka inkorporirali sa aplikacijom potrebno je dodatno implementirati logiku.

Kada sa prethodne slike desnim klikom miša odaberemo opciju View Code dobijamo implementaciju klase NorthWindKlaseDataContext. U klasi je potrebno implementirati metode za izvlačenje svih Narudžbi (Odrers), Dobavljača i Zaposlenika. Implementacija je prikazana u sljedećoj slici:

namespace NorthWindWPFPrimjer
{
    using System.Collections.ObjectModel;
    using System.Linq;

    partial class NorthWindKlaseDataContext
    {
        public ObservableCollection<Order> IzvuciNarudze()
        {
            var query = from p in Orders select p;
            return new ObservableCollection<Order>(query);
        }
        public ObservableCollection<Customer> IzvuciDobavljace()
        {
            var query = from p in this.Customers select p;
            return new ObservableCollection<Customer>(query);
        }
        public ObservableCollection<Employee> IzvuciZaposenike()
        {
            var query = from p in this.Employees select p;
            return new ObservableCollection<Employee>(query);
        }
    }
}

Važna napomena: Nemojte ni u kom slučaju using naredbe postavljati iznad prostora imena NorthWindWPFPrimjer jer će vam se izgubiti datoteka dizajnera i vaš model će nestati. Nažalost ovo je bug koji se nalazi u Visual Studio 2008 SP1.

Na ovaj način implementirali smo metode za izvlačenja podataka iz baze podataka. Sada je na redu da to implementiramo u window1.xaml datoteci. Da bi prikazali podatke o naružbama u window1.xaml datoteci potrebno je formirati objekat ObjectDataProvider kojim ćemo povezati kontrole za prikaz narudžbi. Naravno, ovaj tutorijal neće ulaziti u detalje i objašnjenja svih pojmova i tipova podataka, jer to i nije svrha. Možemo kazati da ObjectDataProvider obuhvata i formira objekat koji omogućuje povezivanje sa izvorom podataka. U WPF izvor podataka mogu biti klasični izvori podataka: vanjska baza podataka, XML datoteka, in memory kolekcija podataka, kao i svaka windowsova standardna ili kastomizirana kontrola, odnosno bilo koji .NET objekat. Ovo je i jedan od najvažnijih aspekata ove tehnologije. Kako smo na samom početku rekli XAML kao novi prograski jezik nije zamjena za klasične .NET jezike, on u biti predstavlja jedno proširenje .NET programskim jezicima, tako da sve što se može implementirati pomoću XAML-a možemo to učiniti i sa C# odnosno VB jezikom, dok obrnuto ne vrijedi.

Prilikom razvijanja aplikacije u WPF, implementacija u XAML datoteci označava deklarativnu implementaciju dok se implementacija u korespondentnom C# ili VB jeziku zove code-behind implementacija.

Dizaj aplikacije

Sada je potrebno našu aplikaciju dizajnirati, u principu potrebno je definisati kontrole koje će kasnije biti povezane sa bazom podataka odnosno sa tabelama i kolonama. Dizaj aplikacije sastoji se od jedne ListBox kontrole u kojoj će biti prikazane sve narudžbe, dok će sa desne strane biti polja u kojim će biti prikazani detalji svake narudžbe pojedinačno. Donja slika prikazuje dizaj prozora.

Sa desne strane postavili smo detalje svake narudžbe od kojih je najinteresantniji dio kolone Customer i Employee koji predstavljeju vanjske ključeve za tabele Customer i Employee. Preko ComboBoxa ostvarićemo ovu relaciju, gdje ćemo u combobox listi prikazati sve partnere (Customers) i zaposlenike (Employees). Obzirom da u standardnom setu kontrola ne postoji kontrola izbora Datuma i Vremena koristimo običnu edit kontrolu Text Box za prikaz datuma.

Cjelokupan dizajn gornjeg prozora uradili smo u XAML datoteci dok ćemo logiku aplikacije implementirati u cs datoteci ili tzv. Code-behind datoteci. Ovo je klasičan primjer upotrebe WPF tehnologije. Dizaj aplikacije implementirati u XAML, a logiku u code-behind datoteci.

Povezivanje sa bazom podataka

Da bi povezali aplikaciju sa bazom podataka potrebno je formirati objekat klase NorthWindKlaseDataContext preko kojeg ćemo vršiti upit prema bazi podataka. Ovdje ćemo pokazati dva načina implementacije. Jedan način je da u code-behind formiramo objekat te povežemo kontrole sa izvorom podataka, a drugi da u XAML-u uradimo isto. Oba načina su ispravna i nema nikave razlike u pogledu performansi. Na sljedećem ispisu prikazan je code-behind način. Listing datoteke Window1.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Data.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace NorthWindWPFPrimjer
{
    public partial class Window1 : Window
    {
        NorthWindKlaseDataContext dx = new NorthWindKlaseDataContext();
        public Window1()
        {
            InitializeComponent();

            listBox1.ItemsSource = dx.IzvuciNarudze();
            listBox1.DisplayMemberPath = "OrderID";

            Binding binding = new Binding();
            binding.Source = listBox1.ItemsSource;
             //OrderID
            binding.Path = new PropertyPath("OrderID");
            textBox1.SetBinding(TextBox.TextProperty, binding);

            //OrderID
            Binding binding1 = new Binding();
            binding1.Source = listBox1.ItemsSource;
            binding1.Path = new PropertyPath("CustomerID");
            comboBox1.ItemsSource = dx.IzvuciDobavljace();
            comboBox1.DisplayMemberPath = "ContactName";
            comboBox1.SelectedValuePath="CustomerID";
            comboBox1.SetBinding(ComboBox.SelectedValueProperty, binding1);

            //EmployeeID
            Binding binding2 = new Binding();
            binding2.Source = listBox1.ItemsSource;
            comboBox2.ItemsSource = dx.IzvuciZaposenike();
            comboBox2.DisplayMemberPath = "FirstName";
            comboBox2.SelectedValuePath = "EmployeeID";
            binding2.Path = new PropertyPath("EmployeeID");
            comboBox2.SetBinding(ComboBox.SelectedValueProperty, binding2);

            Binding binding3 = new Binding();
            binding3.Source = listBox1.ItemsSource;
            //OrderDate
            binding3.Path = new PropertyPath("OrderDate");
            textBox2.SetBinding(TextBox.TextProperty, binding3);

            Binding binding4 = new Binding();
            binding4.Source = listBox1.ItemsSource;
            //ShipVia
            binding4.Path = new PropertyPath("ShipVia");
            textBox3.SetBinding(TextBox.TextProperty, binding4);

            Binding binding5 = new Binding();
            binding5.Source = listBox1.ItemsSource;
            //Freight
            binding5.Path = new PropertyPath("Freight");
            textBox4.SetBinding(TextBox.TextProperty, binding5);

            Binding binding6 = new Binding();
            binding6.Source = listBox1.ItemsSource;
            //ShipPostalCode
            binding6.Path = new PropertyPath("ShipPostalCode");
            textBox5.SetBinding(TextBox.TextProperty, binding6);
        }
    }
}

Pokrenimo aplikaciju iz VS i dobićemo sljedeću sliku:

Ako pokušamo da selektujemo drugu stavku iz liste automatski cemo dobti druge vrijednosti u desnim kontrolama. Ovo automatsko sihroniziranje selektovane stavke iz liste i kontrola obezbjedjeno je formiranjem ObservableCollection klase te osobine liste IsSynchronizedWithCurrentItem=”True”.

Sljedeći listing prikazuje prethodno ali implementirano u XAML-u.

<Window x:Class="NorthWindWPFPrimjer.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:NorthWindWPFPrimjer"
    Title="NorthWind Prijmer" Height="357" Width="514" Closing="Window_Closing">
    <Window.Resources>
        <!--Formiranje ObjectDataProvidera preko NazivaMetode bez parametara-->
        <ObjectDataProvider x:Key="listaNarudzbi"
                            ObjectType="{x:Type local:NorthWindKlaseDataContext}" MethodName="IzvuciNarudzbe"/>
        <ObjectDataProvider x:Key="listaPartnera"
                            ObjectType="{x:Type local:NorthWindKlaseDataContext}" MethodName="IzvuciDobavljace"/>
        <ObjectDataProvider x:Key="listaZaposlenika"
                            ObjectType="{x:Type local:NorthWindKlaseDataContext}" MethodName="IzvuciZaposenike"/>
    </Window.Resources>
    <Grid>
        <!--Rešetka za raspored kontrola-->
        <Grid.RowDefinitions>
            <RowDefinition Height="40" />
            <RowDefinition Height="40" />
            <RowDefinition Height="40" />
            <RowDefinition Height="40" />
            <RowDefinition Height="40" />
            <RowDefinition Height="40" />
            <RowDefinition Height="40" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="100" />
            <ColumnDefinition Width="150" />
        </Grid.ColumnDefinitions>
        <!--List box kontrola za naslovom-->
        <TextBlock Margin="0,0,0,0" Name="textBlock1" Text="Lista Naružbi"
                   VerticalAlignment="Center" HorizontalAlignment="Center"
                   FontSize="16" FontWeight="Bold" Foreground="Chocolate" />
        <ListBox Name="listBox1" Grid.RowSpan="7" Grid.Row="1" IsSynchronizedWithCurrentItem="True"
                 ItemsSource="{Binding Source={StaticResource listaNarudzbi}}" DisplayMemberPath="OrderID" />

        <!--Labele za oznakama polja za editovanje-->
        <Label Grid.Column="1" Margin="10,12,0,0" Name="label1" HorizontalContentAlignment="Right"
               VerticalContentAlignment="Center">OrderID:</Label>
        <Label Margin="6,6,4,6" Name="label2" Grid.Column="1" Grid.Row="1" HorizontalContentAlignment="Right"
               VerticalContentAlignment="Center">Customer:</Label>
        <Label Margin="6,12,4,0" Name="label3" Grid.Column="1" Grid.Row="2" HorizontalContentAlignment="Right"
               VerticalContentAlignment="Center">Employee:</Label>
        <Label Margin="6,6,4,6" Name="label4" Grid.Column="1" Grid.Row="3" HorizontalContentAlignment="Right"
               VerticalContentAlignment="Center">Order Date:</Label>
        <Label Margin="6,6,4,6" Name="label5" Grid.Column="1" Grid.Row="4" HorizontalContentAlignment="Right"
               VerticalContentAlignment="Center">Ship Via:</Label>
        <Label Margin="6,6,4,6" Name="label6" Grid.Column="1" Grid.Row="5" HorizontalContentAlignment="Right"
               VerticalContentAlignment="Center">Freight:</Label>
        <Label Margin="6,12,4,0" Name="label7" Grid.Column="1" Grid.Row="6" HorizontalContentAlignment="Right"
               VerticalContentAlignment="Center">Postal Code:</Label>
        <!--Kontrole za modifikaciju i prikaz kolona narudžbe-->
        <TextBox Margin="0,8,0,8" Grid.Column="2" Grid.Row="0" Name="textBox1"
                 Text="{Binding Source={StaticResource listaNarudzbi}, Path=OrderID}" />
        <ComboBox Margin="0,8,0,8" Grid.Column="2" Grid.Row="1"  Name="comboBox1"
                  ItemsSource="{Binding Source={StaticResource listaPartnera}}"
                  SelectedValuePath="CustomerID"
                  SelectedValue="{Binding Source={StaticResource listaNarudzbi}, Path=CustomerID}"
                  DisplayMemberPath="ContactName"/>
        <ComboBox Margin="0,8,0,8" Name="comboBox2" Grid.Column="2" Grid.Row="2"
                  ItemsSource="{Binding Source={StaticResource listaZaposlenika}}"
                  SelectedValuePath="EmployeeID" SelectedValue="{Binding Source={StaticResource listaNarudzbi},  Path=EmployeeID}"
                  DisplayMemberPath="FirstName"/>
        <TextBox Margin="0,8,0,8" Name="textBox2" Grid.Column="2" Grid.Row="3"
                 Text="{Binding Source={StaticResource listaNarudzbi}, Path=OrderDate }" />
        <TextBox Margin="0,8,0,8" Name="textBox3" Grid.Column="2" Grid.Row="4"
                 Text="{Binding Source={StaticResource listaNarudzbi}, Path=ShipVia}" />
        <TextBox Margin="0,8,0,8" Name="textBox4" Grid.Column="2" Grid.Row="5"
                 Text="{Binding Source={StaticResource listaNarudzbi}, Path=Freight}" />
        <TextBox Margin="0,8,0,8" Name="textBox5" Grid.Column="2" Grid.Row="6"
                 Text="{Binding Source={StaticResource listaNarudzbi}, Path=ShipPostalCode}" />
        <!--Dugmad za brisanje i dodavanje stavki u bazu podataka-->
        <Button Grid.Column="2" Grid.Row="7" Margin="65,6,23,10" Name="button1">Obriši</Button>
        <Button Margin="21,6,17,10" Name="button2" Grid.Column="1" Grid.Row="7" Click="button2_Click">Nova</Button>
    </Grid>
</Window>

Podcrtani XAML kod označava novi kod koji smo dodali pri povezivanju kontrola sa bazom podataka.

Na samom početku definisali smo ObjectDataProvide preko NazivaMetode, i to za svaku tabelu. ObjectDataProvider uvijek se definiše unutar resursa Kontrole roditelja te na taj način objekt postaje vidljiv u svim kontrolama koje pripadaju datom roditelju.

Definisanje logike aplikacije

Prethodno smo definisali model baze podataka preko LINQtoSQL dizajnera, dodatno implementirali poslovnu logiku modela. Zatim smo dizajnirali našu aplikaciju na način da smo definisali raspored i vrste kontrola. Poslije toga povezali smo bazu podataka sa aplikacijom i prikazali podatke preko kontrola.

Za ovaj tutorijal ostaje da implementiramo CRUD operacije odnosno modifikaciju podataka, brisanje i dodavanje redova u bazu podataka. Za početak implementirajmo modifikaciju podataka na način da kad se aplikacija zatvori, da se provjeri da li su podaci mijenjani u odnosu na originalne podatke i baze podataka te na osnovu toga izvršiti modifikaciju baze podataka.

Za ovo nam je potrebno implementirati Closing dogadjaj.

Implementacija unosa novog reda u tabeli

Da bi uopće manipulisali prikazanim podacima u našoj aplikaciji potrebno je dobiti referencu na ListCollectionView a koja predstavlja tekuće podatke u našoj aplikaciji. Takodjer ako smo povezivanje uradili u XAML-u potrebno je definisati referencu na objekat NorthWindKlaseDataContext, a koju ćemo dobiti iz ObjectDataProvidera.

private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
 {
  //U koliko je u toku zatvaranja prozora jos uvijek postojala modifikacija odredjene stavke
  //potrebo napraviti Refresh da bi sve promjene bile aktualizirane
  defaultView.Refresh();

  //Provjera da li postoji bilo kakva promjena koja bi se trebala zapisati u bazu podataka
  int br = dataContext.GetChangeSet().Deletes.Count +
           dataContext.GetChangeSet().Inserts.Count +
           dataContext.GetChangeSet().Updates.Count;

  if (br > 0)
    {
      if (MessageBox.Show("Da li želite pohraniti nastale promjene?",
           "WPF Primjer", MessageBoxButton.YesNo) == MessageBoxResult.Yes)
        {
            dataContext.SubmitChanges();
        }
    }
 }

Iz tog razloga potrebno je na novou Windows objekta formirati članove na sljedeći način:

//Referenca na DefaultView koja je definisan u list kontroli
ListCollectionView defaultView;
//Referenca da DataContext
NorthWindKlaseDataContext dataContext;

Prilikom učitavanja aplikacije odnosno pozivanja dogadjaja Loaded potrebno je ove reference definisati:

private void Window_Loaded(object sender, RoutedEventArgs e)
 {
   //Referenca na DefaultView
   defaultView = (ListCollectionView)CollectionViewSource.GetDefaultView(listBox1.ItemsSource);
   dataContext = (NorthWindKlaseDataContext)(FindResource("listaNarudzbi") as ObjectDataProvider).ObjectInstance;

    Debug.Assert(defaultView != null);
    Debug.Assert(dataContext != null);
 }

Dodavanje nove stavke vrši se jednostavno na sljedeći način:

//Dodavanje nove stavke
private void button2_Click(object sender, RoutedEventArgs e)
 {
   //dodavanje nove stavke
   Order newOrder = (Order)defaultView.AddNew();
   //novoformiranu stavku pohraniti u bazu podataka prvi put kada se pozove SubmitCange
   dataContext.Orders.InsertOnSubmit(newOrder);

 }

Pozivom AddNew metode iz defaltView i novu stavku potrebno je registrovati u DataContext da bi se pohranile promjene. Kada pokrenemo aplikaciju i kliknemo da dugme Dodaj, u listBox na kraju se pojavi nova stavka. Sa desne strane popunimo polja. Prilikom zatvaranja aplikacija pojavljuje se upit za pohranu podataka.

Implementacija brisanja stavke

Birisanje stavke iz liste vršimo listingom prikazanim na sljedećoj slici:

//Brisanje tekuce stavke
private void button1_Click(object sender, RoutedEventArgs e)
  {
    if (MessageBox.Show("Da li želite obrisati tekuću stavku?",
         "WPF Primjer", MessageBoxButton.YesNo) != MessageBoxResult.Yes)
          return;
    //Prvo je potrebno objekat da se objekat oznaci obrisan u DataContext
    dataContext.Orders.DeleteOnSubmit((Order)defaultView.CurrentItem);
    //Označeni objekat brisemo iz DafaulView
    defaultView.Remove(defaultView.CurrentItem);

  }

Pokrenemo aplikaciju te klikom na dugme Obriši pojavljuje se poruka za potvrdu brisanja stavke.

Zaključak

Ovim jednostavnim primjerom prikazali smo jedan od načina implementacije LINQ to SQL u WPF. Sljedećim postovima obradićemo manipulaciju sa podacima u smislu sortiranja grupiranja podataka. Također u narednim postovima govorićemo o konvertorima, kastomizaciji listbox kontrole i sl.

Izvorni kod za ovaj primjer možete skinuti sa ovog linka.