sobota, 6 marca 2010

Praca z Menu w WPF

Każda nowoczesna aplikacja powinna mieć dobrze zaprojektowane i wygodne w obsłudze menu. W WPF bez trudu zaprojektujemy atrakcyjne menu, które nie tylko ułatwi poruszanie się użytkownikowi po naszej aplikacji, ale sprawi że będzie częściej sięgał po naszą aplikację.

Menu w wygodny sposób można zaprojektować z użyciem XAMLa.  

        <Menu>

        </Menu>

Pomiędzy znaczniki Menu możemy wstawiać kolejne elementy naszego menu:

        <Menu>
            <MenuItem Header="Plik" Name="file"></MenuItem>
            <MenuItem Header="Widok" Name="view"></MenuItem>
            <MenuItem Header="Pomoc" Name="help"></MenuItem>
        </Menu>

Użytkownikowi wyświetli się oczywiście to co zawarliśmy we właściwości Header. Aby dodać zdarzenie dla elementu menu, wystarczy wpisać Click=" a edytor sam zaproponuje stworzenie odpowiedniego EvntHandlera. 

Tworzenie menu wielopoziomowego także nie jest trudne. Wystarczy w konkretnym MenuItem-ie wprowadzić kolejny MenuItem:

        <Menu>
            <MenuItem Header="Plik" Name="file">
                <MenuItem Header="Otwórz" Name="open"></MenuItem>
                <MenuItem Header="Zapisz" Name="save"></MenuItem>
            </MenuItem>
            <MenuItem Header="Widok" Name="view"></MenuItem>
            <MenuItem Header="Pomoc" Name="help"></MenuItem>
        </Menu>

Jeśli chcielibyśmy uzyskać kolejny poziom dodajemy po prostu następny zagnieżdżony znacznik MenuItem. 

Często oprócz opisu tekstowego menu zawierają jeszcze pictogram tekstowego znaczenia. Ułatwia to wyszukiwanie konkretnych opcji w rozbudowanych menach a zarazem jest przyjemne dla oka. Aby dodać pictogram należy skorzystać z właściwości Icon:

        <Menu>
            <MenuItem Header="Plik" Name="file">
                <MenuItem Header="Otwórz" Name="open">
                    <MenuItem.Icon>
                        <Image Source="open.png"></Image>
                    </MenuItem.Icon>
                </MenuItem>
                <MenuItem Header="Zapisz" Name="save">
                    <MenuItem.Icon>
                        <Image Source="sae.png"></Image>
                    </MenuItem.Icon>
                </MenuItem>
            </MenuItem>
            <MenuItem Header="Widok" Name="view"></MenuItem>
            <MenuItem Header="Pomoc" Name="help"></MenuItem>
        </Menu>

Praca z Bitmapami w WPF

Dzisiaj trochę informacji, jak w WPF zabrać się za obróbkę zdjęć i ogólne aspekty związane z kontrolką Image.

1. Wczytywanie obrazka do kontrolki Image:
Zadanie to można wykonać na kilka sposobów: strumień, pobranie z żądanej lokalizacji sieciowej lub ścieżka. Najprostszą i najczęściej stosowaną opcją jest wczytanie ze ścieżki:


                        Image image = new Image();
                        BitmapImage bitmapImage = new BitmapImage(new Uri("c:\obraz.png"));
                        image.Source = bitmapImage;

2. Zapisywanie zawartości kontrolki Image do pliku.
Tutaj niestety bez pomocy strumienia się nie obejdzie. 
                SaveFileDialog saveFileDialog = new SaveFileDialog();
                saveFileDialog.Filter = "Pliki BMP | *.bmp | Pliki PNG | *.png";
                if (saveFileDialog.ShowDialog(this) == true)
                {
                    FileStream saveStream = new FileStream(saveFileDialog.FileName, FileMode.OpenOrCreate);
                    BmpBitmapEncoder encoder = new BmpBitmapEncoder();
                    encoder.Frames.Add(BitmapFrame.Create(Image.Image));
                    encoder.Save(saveStream);
                    saveStream.Close();
                }

Kod trochę bardziej skomplikowany od tego znanego w WindowsForms, ale działa na takiej samej zasadzie :) Oczywiście jeżeli chcemy zapisać obrazek w innym formacie, możemy skorzystać z innego enkodera.

3. Dopasowanie rozmiarów obiektu Image do wczytywanego obrazka

                    Bitmap = new BitmapImage(new Uri(openFileDialog.FileName));

                    Image.Width = Bitmap.Width;
                    Image.Height = Bitmap.Height;

4. Konwersja bitmap
W WinForms istniała dziecinnie prosta możliwość zmiany wartości pojedyńczych pixeli na naszym obrazku.
Dla zobrazowania tego co chcemy zrobić przypuśćmy scenariusz w którym chcielibyśmy zmienić skalę kolorów na szare. W WinForms można by to zrobić w taki sposób:

            Stopwatch clock = new Stopwatch();
            clock.Start();
            Bitmap img = new Bitmap(Image.FromFile(@"c:\o.png"));
            int rows = img.Width;
            int cols = img.Height;
            Color c;
            int b;
            for (int i = 0; i < rows; i++)
            {
                for (int j = 0; j < cols; j++)
                {
                    c = img.GetPixel(i, j);
                    b = (c.R + c.G + c.B) / 3;
                    img.SetPixel(i, j, Color.FromArgb(b, b, b));
                }
            }
            pictureBox1.Image = img;
            clock.Stop();
            MessageBox.Show(clock.ElapsedMilliseconds.ToString());

Jak widać dołożyłem do kodu Stopwatch aby sprawdzić ile trwa konwersja do skali szarości w tym rozwiązaniu. Podczas konwersji wykorzystuje bitmapę o wielkości 512 x 512:

Jak widać średni czas konwersji takiego imaga zajmuje > 600 ms. Nie jest to zadowalający czas. Należy też wziąć pod uwagę fakt, że zdjęcia w większości przypadków są większej rozdzielczości. Nasz sposób sprawia, że wraz ze zwiększaniem rozdzielczości będzie rósł czas konwersji. Dzieje się tak z powodu powolnego działania funkcji GetPixel i SetPixel. Istnieje kilka rozwiązań tego problemu. Można skorzystać z kodu unsafe lub bardzo rozbudowanego mechanizmu oferowanego przez klasę ColorMatrix.

W WPF niestety (albo na szczęście) nie istnieją metody GetPixel i SetPixel.Aby zmienić wartość pojedynczego pixela należy użyć klasy WriteableBitmap, która posiada dwie metody: CopyPixels i WritePixels. Pierwsza z nich kopiuje wartości pixeli z Bitmapy do tablicy a druga nadpisuje wartości pixeli w Bitmapie.Zwrócić należy tutaj na słowo wartości pixeli. Otóż każdy pixel jest reprezentowany przez 3 składowe RGB i A (alpha - przeźroczystość). Czyli dla przykładu pierwszy pixel będzie zapisany na pierwszych 4 pozycjach naszej tablicy, drugi na kolejnych 4 itd.
Zobaczmy na przykład:

            BitmapImage image = new BitmapImage(new Uri(@"C:\Users\Public\Pictures\Sample Pictures\Desert.jpg"));
            width = (int)image.Width;
            height = (int)image.Height;
            _bitmap = new WriteableBitmap(image);
            stride = width * ((this._bitmap.Format.BitsPerPixel + 7) / 8);
            int arraySize = stride * height;
            byte[] pixels = new byte[arraySize];
            _bitmap.CopyPixels(pixels, stride, 0);
            int color = 0;
            int j = 0;
            for (int i = 0; i < pixels.Length / 4; ++i)
            {
                color = (pixels[j] + pixels[j + 1] + pixels[j + 2]) / 3;
                pixels[j] = (byte)color;
                pixels[j + 1] = (byte)color;
                pixels[j + 2] = (byte)color;
                pixels[j + 3] = 255;
                j += 4;
            }

            Int32Rect rect = new Int32Rect(0, 0, width, height);
            _bitmap.WritePixels(rect, pixels, stride, 0);
            Image.Source = _bitmap;

Kod może trochę bardziej rozbudowany od znanego z WinForms ale za to działający znacznie szybciej. O ile szybciej? O tym przekonamy się podczas tego testu:
Jak widać czas wynosi około 42 ms co jest dużo lepszym rezultatem. Kolejne czasy na poziomie 9 - 13 ms są wynikiem używania przez WPF Intelligent Redrawing (więcej o tym narzędziu można poczytać na stronach msdn).
 Jeżeli mowa o zamianie na skalę szarości nie można pominąć tego co sam framework oferuje. Dzięki klasie FormatConvertedBitmap mamy możliwość konwersji obrazka na dowolną skalę. Przykład:



            BitmapImage image = new BitmapImage(new Uri(@"C:\Users\Public\Pictures\Sample Pictures\Desert.jpg"));
            FormatConvertedBitmap format = new FormatConvertedBitmap();
            format.BeginInit();
            format.Source = image;
            format.DestinationFormat = PixelFormats.Gray32Float;
            format.EndInit();
            Image.Source = format;

Kod jak widać bardzo prosty, ciekawe jak z wydajnością?:

Wydajność całkiem niezła, czas konwersji zmniejszył się ponad 4 krotnie. A kolejne przemalowania nie zabierają praktycznie żadnego czasu (jedynie kiedy badamy takty procesora można wychwycić jakieś zmiany). 
Oczywiście naszą metodę można przyśpieszyć poprzez użycie wskaźników w trybie unsafe.

Memento Pattern

Często stajemy przed problemem, kiedy chcemy udostępnić użytkownikowi naszej aplikacji cofnięcie jakiejś decyzji. Największe znaczenie taka operacja pełni w grach komputerowych, gdzie zapisywane są tzw. checkpoints dzięki którym gracz jest w stanie cofnąć się do wcześniejszej fazy gry.
W swoim aktualnym projekcie, potrzebowałem stworzyć mechanizm umożliwiający cofnięcie zmian wprowadzonych w obrazie. Dzięki wzorcowi Memento można w bardzo łatwy i szybki sposób uzyskać taką funkcjonalność. 


Co nam daje Memento?
Pozwala na zachowanie całego stanu obiektu (lub tyle ile potrzebujemy), w celu późniejszego jego odtworzenia, bez naruszenia zasad enkapsulacji.
Spójrzmy na poniższy diagram klas:

Mamy tutaj 3 klasy:
Orginator - jest to obiekt którego stan wewnętrzny chcemy zachować. Poprzez metodę CreateMemennto() tworzy "kopię zapasową" samego siebie a dzięki metodzie SetMemento(Memento) może się odtworzyć z kopi. 

Memento - obiekt który jest kopią oryginalnego obiektu. Dzięki metodą GetState() i SetState() pozwalają na odczyt i zapis stanu obiektu.



Caretaker - obiekt ten zarządza obiektami Memento


Przykłady zawsze lepiej działają na wyobraźnię, dlatego spójrzmy na oto taki przykład. Mamy aplikację w której użytkownik wypełnia jakieś pola. Niech to będzie Imię, Nazwisko i wiek. Przyjmijmy, że wygląda ona tak:


Przyjmijmy, że po naciśnięciu przycisku dane zapisują się do bazy danych. Teraz nasz użytkownik wpisuje swoje dane, wciska ok i nagle przypomina mu się że źle wpisał nazwisko. Są dwa wyjścia: wpisze wszystko od początku albo kliknie przycisk cofnij i wpisze poprawne dane. W tym konkretnym przypadku nie wiele będzie kosztowała go pomyłka, jeżeli jednak pól by było więcej to możliwość cofnięcia wprowadzonych danych będzie z pewnością bardzo przydatna. Kod aplikacji:

    public class Person

    {

        private string _firstName;

        private string _lastName;

        private int _age;

 

        public string FirstName

        {

            get

            {

                return _firstName;

            }

            set

            {

                _firstName = value;

            }

        }

 

        public string LastName

        {

            get

            {

                return _lastName;

            }

            set

            {

                _lastName = value;

            }

        }

 

        public int Age

        {

            get

            {

                return _age;

            }

            set

            {

                _age = value;

            }

        }

 

        /// <summary>

        /// Przywraca poprzedni stan obiektu

        /// </summary>

        /// <param name="memento"></param>

        public void SetMemento(Memento memento)

        {

            this._age = memento.Age;

            this._firstName = memento.FirstName;

            this._lastName = memento.LastName;

        }

 

        /// <summary>

        /// Zapisuje aktualny stan obiektu

        /// </summary>

        /// <returns></returns>

        public Memento CreateMemento()

        {

            return new Memento(_firstName, _lastName, _age);

        }

    }


    public class Memento

    {

        private string _firstName;

        private string _lastName;

        private int _age;

 

        public string FirstName

        {

            get

            {

                return _firstName;

            }

            set

            {

                _firstName = value;

            }

        }

 

        public string LastName

        {

            get

            {

                return _lastName;

            }

            set

            {

                _lastName = value;

            }

        }

 

        public int Age

        {

            get

            {

                return _age;

            }

            set

            {

                _age = value;

            }

        }

 

        public Memento(string firstName, string lastName, int age)

        {

            _firstName = firstName;

            _lastName = lastName;

            _age = age;

        }

 

 

    }


    public class Caretaker

    {

        private Memento _memento;

        public Memento Memento

        {

            get

            {

                return _memento;

            }

            set

            {

                _memento = value;

            }

        }

    }



    public partial class Form1 : Form
    {
        Person person;
        Caretaker[] caretaker;
        int counter = 0;
        public Form1()
        {
            InitializeComponent();
            person = new Person();
            caretaker = new Caretaker[10];
        }

        private void DisplayPersonInformation()
        {
            textBox1.Text = person.FirstName;
            textBox2.Text = person.LastName;
            textBox3.Text = person.Age.ToString();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            if (counter<10)
            {
                caretaker[counter] = new Caretaker();
                caretaker[counter].Memento = person.CreateMemento();
                counter++;
                person.FirstName = textBox1.Text;
                person.LastName = textBox2.Text;
                person.Age = int.Parse(textBox3.Text);
            }

        }

        private void button2_Click(object sender, EventArgs e)
        {
            if (counter>1)
            {
                counter--;
                person.SetMemento(caretaker[counter].Memento);
                DisplayPersonInformation();
            }
        }
    }

Przykład być może nie jest najwyższych lotów, jednak w prosty sposób obrazuje możliwość wykorzystania tego wzorca w swoich projektach. Jak pisałem wcześniej w swoim projekcie potrzebowałem możliwości cofania zmian wprowadzanych na obrazie. Dla takich zastosowań memento nadaje się idealnie :)

środa, 3 marca 2010

Tworzenie web serwisu WCF cz. 3

W tej części kursu zajmiemy się "konsumpcją" stworzonego przez nas wcześniej serwisu opartego o WCF. Jak wcześniej wspominałem, stworzyliśmy WCF Library Application co daje nam spore możliwości odnośnie wykorzystania naszego web serwisu. Tutaj, postaram się w miarę jasno pokazać jak użyć naszej aplikacji do stworzenia prostej aplikacji ASP.NET i WinForms.

ASP.NET
Zaczniemy od aplikacji webowej. 
1. Otwieramy stworzony przez nas ostatnio projekt, klikamy prawym przyciskiem myszy i wybieramy Add New Project...
2. Wybieramy ASP.NET Web Application, nadajemy nazwę i klikamy Ok. Po zatwierdzeniu powinniśmy otrzymać taki widok naszych projektów:


Aplikację podzieliłem na 3 oddzielne podstrony:

Pierwsza będzie odpowiedzialna za dodanie nowego klienta do bazy danych. Druga pozwoli na wyświetlenie klienta o określonym Id. Ostatnia wyświetli wszystkich klientów w bazie. A więc do dzieła :)


3. Usuwamy z naszego projektu plik Default.aspx i dodajemy nowy plik: MasterPage.Master. Pozwoli nam to na stworzenie menu, które będzie na każdej podstronie naszego serwisu, bez konieczności kopiowania na wszystkie podstrony kodu odpowiedzialnego za menu.
4. Do utworzonego MasterPaga dodajemy prostą tabelkę z jednym wierszem i dwiema komórkami:


<%@ Master Language="C#" AutoEventWireup="true"  
CodeBehind="MasterPage.master.cs" Inherits="
WebPage.MasterPage" %>
 
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 
Transitional//EN" "http://www.w3.org/TR/xhtml1/
DTD/xhtml1-transitional.dtd">
 
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title></title>
    <asp:ContentPlaceHolder ID="head" runat="server">
    </asp:ContentPlaceHolder>
</head>
<body>
    <form id="form1" runat="server">
    <table border="2" cellpadding="10">
    <tr align="left" valign="top">
    <!--Menu naszej strony -->
    <td>
        <asp:HyperLink ID="HyperLink1" runat=
"server">Add new customer</asp:HyperLink>
        <br />
        <asp:HyperLink ID="HyperLink2" runat=
"server">Customer information</asp:HyperLink>
        <br />
        <asp:HyperLink ID="HyperLink3" runat=
"server">Show all customer</asp:HyperLink>
    </td>
    <!-- Zawartość strony -->
    <td>
    <asp:ContentPlaceHolder runat="server"  
ID="contetnPlaceHolder"></asp:ContentPlaceHolder>
    </td>
    </tr>
    </table>
    </form>
</body>
</html>
 
Pierwsza komórka zawiera proste menu, druga resztę zawartości.
5. Dodajemy nowy plik do projektu typu WebContent Form. Po nadaniu nazwy pojawi się okienko w którym wybieramy nasz MasterPage i akceptujemy wybór.
6. Do ContentPlaceHoldera dodajemy po dwie kontrolki Label i TextBox oraz jeden przycik:

<%@ Page Title="" Language="C#" MasterPageFile=
"~/MasterPage.Master" AutoEventWireup="true" 
CodeBehind="AddNewCustomer.aspx.cs" Inherits=
"WebPage.AddNewCustomer" %>
<asp:Content ID="Content1" ContentPlaceHolderID=
"head" runat="server">
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID=
"contetnPlaceHolder" runat="server">
    <asp:Label ID="Label1" runat="server" Text=
"Imię: "></asp:Label>
    <asp:TextBox runat="server" ID="tbFistName">
</asp:TextBox>
    <br />
    <br />
    <asp:Label runat="server" ID="Label2" Text=
"Nazwisko"></asp:Label>
    <asp:TextBox runat="server" ID="tbLastName">
</asp:TextBox>
    <br />
    <br />
    <asp:Button runat="server" ID="Submit" Text="Wyślij"/>
</asp:Content>

7. Następnie dodajemy dwie kolejne podstrony: CustomerInfo i AllCustomers tak jak w poprzednim kroku:
CustomerInfo:

<%@ Page Title="" Language="C#" MasterPageFile=
"~/MasterPage.Master" AutoEventWireup="true" 
CodeBehind="CustomerInfo.aspx.cs" Inherits=
"WebPage.CustomerInfo" %>
<asp:Content ID="Content1" ContentPlaceHolderID=
"head" runat="server">
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID=
"contetnPlaceHolder" runat="server">
    <asp:Label ID="Label1" runat="server" Text="Id: ">
</asp:Label>
&nbsp;<asp:TextBox ID="TextBox1" runat="server" 
        ontextchanged="TextBox1_TextChanged">
</asp:TextBox>
    <br />
    <asp:Button ID="BShow" runat="server"  
Text="Pokaż" />
    <br />
    <asp:Label ID="LId" runat="server"></asp:Label>
    <br />
    <asp:Label ID="LFirstName" runat="server">
</asp:Label>
    <br />
    <asp:Label ID="LLastName" runat="server">
</asp:Label>
    <br />
</asp:Content>
 
AllCustomers:

<%@ Page Title="" Language="C#" MasterPageFile=
"~/MasterPage.Master" AutoEventWireup="true"  
CodeBehind="AllCustomers.aspx.cs" Inherits=
"WebPage.AllCustomers" %>
<asp:Content ID="Content1" ContentPlaceHolderID=
"head" runat="server">
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID=
"contetnPlaceHolder" runat="server">
    <asp:GridView ID="GridView1" runat="server">
    </asp:GridView>
</asp:Content>
8. Kolejnym krokiem jest dodanie referencji do naszego web serwisu. Klikamy prawym klawiszem na naszym projekcie ASP.NET i wybieramy opcje Add Service Reference. Pojawi się nowe okienko w którym klikamy na przycisk Discover. Po tej czynności powinniśmy ujrzeć następujący ekran:

Wprowadzamy wartość Namespace np. MyService i akceptujemy. Do naszego projektu zostanie dodana tzw. klasa Proxy która zajmie się zarządzaniem przesyłania danych pomiędzy web serwisem a naszą aplikacją. 
9. Przechodzimy do naszej pierwszej podstrony, dotyczącej dodawania nowego użytkownika i dodajemy nowe zdarzenie do przycisku Wyślij:

        protected void Submit_Click(object sender, 
EventArgs e)
        {
            MyService.Customer cust = 
new WebPage.MyService.Customer();
            cust.FirstName = tbFistName.Text;
            cust._lastName = tbLastName.Text;
            MyService.CustomerServiceClient client = 
new WebPage.MyService.CustomerServiceClient();
            client.AddNewCustomer(cust);
        }
 
Następnie kod przycisku pokaż dla strony CustomerInfo:

        protected void BShow_Click(object sender,  
EventArgs e)
        {
            MyService.CustomerServiceClient client = 
new WebPage.MyService.CustomerServiceClient();
            MyService.Customer c = 
client.GetCustomerById(int.Parse(tbId.Text));
            LFirstName.Text = c.FirstName;
            LLastName.Text = c._lastName;
            LId.Text = c.ID.ToString();
        }
 
Oraz kod wyświetlający wszystkich klientów:

        protected void Page_Load(object sender,  
EventArgs e)
        {
            MyService.CustomerServiceClient client = 
new WebPage.MyService.CustomerServiceClient();
            GridView1.DataSource = 
client.GetAllCustomers();
            GridView1.DataBind();
        }

9. Jeśli dodaliśmy już potrzebny kod czas na przetestowanie naszej aplikacji. Odpalamy nasz projekt i próbujemy dodać nową osobę. Po przeładowaniu strony, jeśli wcześniej nie pojawi się żaden błąd, nowa osoba zostanie dodana do bazy. Także podczas pobierania informacji o nowej osobie nie powinniśmy mieć żadnych problemów:

10. Przejdźmy do wyświetlenia informacji o wszystkich  klientach. Klikamy na Show all customer i otrzymujemy piękny błąd:


Błąd jest spowodowany tym, że domyślnie nasz web serwis ma ustawione aby maksymalna paczka przesyłana do naszej aplikacji miała 64kb. Ponieważ w bazie jest duża ilość klientów, 64kb to trochę za mało aby ich wszystkich na raz przesłać. Istnieje kilka sposób na poradzenie sobie z tym problemem:
1. CustomPaging w GridView i pobieranie tylko wierszy o wskazanym Id
2. Zwiększenie maksymalnej wielkości przesyłanej od serwisu do naszej aplikacji.
 Tutaj opiszę 2 możliwość, gdyż jest bezpośrednio związana z naszym web serwisem.

Klikamy prawym klawiszem na App.Config w web serwisie i wybieramy EditWCF configuration...
Przechodzimy do części Bindings, wybieramy New Binding Configuration...
Z listy wybieramy pasujący do naszego punktu końcowego format (wsHttpBinding) 
Edytujemy kolejno wartości: MaxBufferPoolSize oraz MaxRecivedMessageSize. Zwiększymy obie wartości do np. 10 MB: 10485760:


Teraz dopisujemy konfigurację do naszego punktu końcowego:


Po tym zabiegu nasz serwis nadal nie będzie działać jeszcze poprawnie. Dzieje się tak dlatego, że wielkość paczki należy ustawić po obu stronach: klienta i serwera. Po stronie serwera mamy już sprawę załatwioną. Czas na klienta:
Tak jak poprzednio klikamy prawym klawiszem myszki na Web.Config i edytujemy właściwości WCF. Tutaj już jednak w Bindingu mamy na gotowe plik konfiguracyjny. Wystarczy tylko zwiększyć poprzednio pokazane wartości. Po zaakceptowaniu zmian możemy uruchomić ponownie nasz projekt.
Po ponownym odpaleniu projektu i wybraniu podstrony wyświetlającej wszystkich klientów otrzymamy ich całą listę:
Tak więc cel został osiągnięty :)


WinForms
1. Do projektu dodajemy nowy projekt WindowsForms.
2. Dodajemy TabControl do formatki: 
3. Dodajemy kontrolki do każdej zakładki oraz referencję do serwisu:
4.  Teraz do każdego przycisku odpowiedni kod (analogicznie jak dla wersji ASP.NET)
5. Zmieniamy wielkość przesyłanych danych do 10 MB po stronie klienta (plik app.config)
6. Kompilujemy i odpalamy naszą aplikację:

 Jak widać tworzenie serwisów w WCF nie jest skomplikowane a niesie za sobą wiele korzyści. WCF jest bardzo elastyczną technologią i skazaną na swój rynkowy sukces.