Hasło - aplikacja MVVM

Na prezentacji pokazałem sposób tworzenia aplikacji WPF z wykorzystaniem Visual Studio, Blend i wzorca projektowego MVVM. Kod i slajdy będą na SkyDrive grupy. Dzisiaj chcę wam pokazać jak można wykonać podobną aplikacje która może nam posłużyć do „wymyślania” haseł na konta ;p
To do dzieła.
1 Tworzymy nowy projekt.
Nowy projekt utworzymy z wykorzystaniem Blenda. Jeżeli ktoś nie ma Blenda może śmiało to samo wykonać w Visual Studio. Uruchamiamy Blenda po czym w okienku które nam wyskoczyło naciskamy New Project. Alternatywnie możemy zrobić to samo przez File-> New Project. Wybieramy typ projektu jako WPF Application. Nazywamy nasz projekt Haslo, zapamiętujemy gdzie go zapisujemy i klikamy OK.
2 Projektujemy widok
Powinno ukazać się nam okno domyślnie utworzone przez Blenda. Będziemy dążyć aby wygląd naszej aplikacji był taki jak niżej.
Tworzyć nasz widok możemy na dwa sposoby, poprzez przeciąganie kontrolek(niektórzy mogą preferować ten sposób) lub przez deklaracje bezpośrednio w kodzie. Ja osobiście wolę ta druga metodę wiec jej będziemy używać. Aby zobaczyć nasz kod klikamy na View-> Active Document View-> Split View ( będziemy widzieć kod i wygląd naszej aplikacji ).  Zabieramy się za tworzenie widoku.  Zaczynamy od zdefiniowania w naszym Gridzie dwóch wierszy do których będziemy przypisywać kontrolki. Uzyskujemy to przez następujący kod:
<Grid.RowDefinitions>
           <RowDefinition Height="Auto"/>
           <RowDefinition Height="auto"/>
</Grid.RowDefinitions>
 Następnie dodajemy dwa StackPanele. Jeden przypisujemy do pierwszego wiersza, drugi do drugiego.
W pierwszym tworzymy Labela który będzie nam wyświetlać losowane liczby. Wielkość czcionki ustawiamy na 40, wyśrodkowujemy w pionie i poziomie. Jego zawartość(Content) musimy połączyć z właściwością ViewModel który będziemy tworzyć w dalszej części. Drugi StackPanel będzie troche bardziej złożony.  W jego wnętrzu deklarujemy dwa kolejne StackPanele. W obu ustawiamy orientację na poziomą i wyrównujemy w poziomie na środek. Do tej pory nasz fragment powinien wyglądać następująco.

 <StackPanel Grid.Row="0">
      <Label Content="{Binding Path=Liczby,UpdateSourceTrigger=PropertyChanged}" FontSize="40" HorizontalContentAlignment="Center"
                  VerticalContentAlignment
="Center"/>
      </StackPanel>
      <StackPanel Grid.Row="1">
      <StackPanel HorizontalAlignment="Center" Orientation="Horizontal">
            <Button Command="{Binding Start}" Height="45" Content="Start" FontSize="25" Margin="0,0,10,0"/>
            <Button Command="{Binding Stop}" Height="45" Content="Stop" FontSize="25" Margin="0,0,10,0"/>
      </StackPanel>
      <StackPanel HorizontalAlignment="Center" Orientation="Horizontal">
            <Slider Value="{Binding Slider}" Width="200" Height="20" Margin="0,10,0,0" />
            <Label Content="{Binding Path=SliderValue, UpdateSourceTrigger=PropertyChanged}" FontSize="18"/>
      </StackPanel>
</StackPanel>

Postępujemy analogicznie jak w przypadku pierwszego. Wypełniamy drugi następującym kodem:
<StackPanel Grid.Row="1">
       <StackPanel HorizontalAlignment="Center" Orientation="Horizontal">
               <Button Command="{Binding Start}" Height="45" Content="Start" FontSize="25" Margin="0,0,10,0"/>
               <Button Command="{Binding Stop}" Height="45" Content="Stop" FontSize="25" Margin="0,0,10,0"/>
        </StackPanel>
        <StackPanel  HorizontalAlignment="Center" Orientation="Horizontal">
                <Slider Value="{Binding Slider}" Width="200" Height="20" Margin="0,10,0,0" />
                <Label Content="{Binding Path=SliderValue, UpdateSourceTrigger=PropertyChanged}" FontSize="18"/>
         </StackPanel>
 </StackPanel> 
 Przy przyciskach pojawila się nowa właściwość – Command.
 Nasz widok powinien być już gotowy. Później będziemy musieli na chwile do niego wrócić alby połaczyć nasz ViewModel z przed chwila utworzonym widokiem. Budujemy nasz projekt (Ctrl+Shift+B)
 
3 Visual Studio
W tej części zajmiemy się logika naszej aplikacji. Otwieramy w Visual Studio projekt który wczesniej utworzyliśmy.  Na początku zajmiemy się utworzeniem naszego ViewModelu później przystąpimy do klas które będą mu potrzebne. Tworzymy nowa klasę :
 
Nazywamy ją MainViewModel i zatwierdzamy.  Nowo utworzona klasa musi implementować interfejs INotifyPropertyChanged. Do tego potrzebne będzie nam dodanie przestrzeni nazw (using System.ComponentModel) . Aby przyśpieszyć sobie prace Visual Studio zaimplementuje go za nas. Klikamy prawym przyciskiem myszki na przed chwila zadeklarowanej implementacji, wybieramy Implement Interface -> Implement Interface.
 
 Dodajemy metodę która będzie wywoływana przy każdej zmianie jakiejkolwiek właściwości. Będzie nam to potrzebne aby nasz widok mógł się zorientować, że zmiana miała miejsce i należy jego dane zaktualizować.
public void PropertyChangedInvoker(string name)
{
    if (PropertyChanged != null)
    {
         PropertyChanged(this, new PropertyChangedEventArgs(name));
    }
}

Teraz kolej nadeszła na utworzenie właściwości w naszej klasie, dodajemy następujący kod:

//wartosc suwaka w postaci double
private double _slider;
public double Slider
{
     get { return _slider; }
     set
     {
          _slider = value;
          SliderValue = Convert.ToInt32(_slider);
     }
}

//pozycja suwaka w postaci int
private int _sliderValue;
public int SliderValue
{
     get { return _sliderValue; }
     set
     {
          _sliderValue = value;
          PropertyChangedInvoker("SliderValue");
     }
}

//liczby które są wyświetlane w widoku
private string _liczby;
public string Liczby
{
     get { return _liczby; }
     set
     {
          _liczby = value;
          PropertyChangedInvoker("Liczby");
     }
}

//odpowiada za trzymanie informacji czy przcisk Stop został nacisniety
private bool _stopButtonClicked;
public bool StopButtonClicked
{
     get { return _stopButtonClicked; }
     set
     {
          _stopButtonClicked = value;
          PropertyChangedInvoker("StopButtonClicked");
     }
}

//odowiada za trzymanie informacji czy przycisk Stop jest aktywny
private bool _canStopButtonExecute;
public bool CanStopButtonExecute
{
     get { return _canStopButtonExecute; }
     set
     {
          _canStopButtonExecute = value;
          PropertyChangedInvoker("CanStopButtonExecute");
     }
}

Naszemu ViewModelowi dalej czegoś brakuje. W Widoku utworzyliśmy 2 przyciski. Teraz pokaże co zrobić żeby je trochę ożywić. Pierwszym zadaniem będzie utworzenie i klas które będą implementowały interface ICommand. Klasy będą nazywać się odpowiednio StartCommand i StopCommand. Sama implementacja wygląda dokładnie tak samo jak wcześniej pokazałem. Wcześniej jednak do każdej z nich musimy dodać następującą przestrzeń nazw - using System.Windows.Input; Po tym nasze klasy, w tym wypadku StartCommnad, powinny wyglądać tak:

class
StartCommand : ICommand
{
    public bool CanExecute(object parameter)
    {
        throw new NotImplementedException();
    }

    public event EventHandler CanExecuteChanged;

    public void Execute(object parameter)
    {
        throw new NotImplementedException();
    }
}

Klasa posiada dwie metody i jeden event. Pierwsza metoda(CanExecute) jest wywoływana gdy event (CanExecuteChanged) zostanie „wzniesiony”. Tak nasz widok sprawdza czy dana komenda może być wykonana.
Druga metoda (Execute) zawiera kod który będzie wywołany w przypadku gdy komenda ma zostać wykonana ( w naszym przypadku przez naciśniecie przycisku).
Musimy trochę rozbudować nasze klasy gdyż puste metody niewiele nam pomogą J. Pierwszym krokiem będzie utworzenie konstruktora i dodanie referencji do naszego ViewModelu dla naszych klas. Pozwoli nam to odwoływać się do jego właściwości.
private readonly MainViewModel _vm;
public StartCommand(MainViewModel viewModel)
{
    _vm = viewModel;
}
Dodamy teraz trochę logiki do naszych metod. Rozbudujemy tez nasz event.

public bool CanExecute(object parameter)
{
    return _vm.CanStopButtonExecute == false && _vm.SliderValue != 10 && _vm.SliderValue != 0;
}

public
event EventHandler CanExecuteChanged
{
    add { CommandManager.RequerySuggested += value; }
    remove { CommandManager.RequerySuggested -= value; }
}

public
void Execute(object parameter)
{
    _vm.CanStopButtonExecute = true;
    System.Threading.ThreadPool.QueueUserWorkItem(_vm.Losowanie);
}
Wytłumaczenia wymagają dwie rzeczy. Pierwsza to co to jest losowanie. Losowanie to będzie metoda która będziemy jeszcze implementować w ViewModelu. Będzie ona odpowiedzialna za losowanie liczb. Drugą rzeczą dla niektórych może być tajemnicze wywołanie metody -System.Threading.ThreadPool.QueueUserWorkItem. Pozwala nam ono wykonać dana metodę w osobnym wątku które będzie „obliczać” swoje rzeczy niezależnie od głównego wątku naszej aplikacji. Więcej informacji znajdziecie we wcześniejszych wpisach z tamtego roku. Dlaczego tak ? Nie chcemy aby nad naszą aplikacja pojawiła się klepsydra :P Nie odpowiadałaby ona na nasze żądania.Teraz do rzeczy bo zostało nam trochę roboty jeszcze. Druga nasza klasa powinna wyglądać podobnie:

class
StopCommand : ICommand
{
    private readonly MainViewModel _vm;
    public StopCommand(MainViewModel viewModel)
    {
         _vm = viewModel;
    }

    public bool CanExecute(object parameter)
    {
         return _vm.CanStopButtonExecute;
    }

   
public event EventHandler CanExecuteChanged
    {
         add { CommandManager.RequerySuggested += value; }
         remove { CommandManager.RequerySuggested -= value; }
    }

   
public void Execute(object parameter)
    {
         _vm.StopButtonClicked = true;
    }
}

Musimy teraz „podpiąć” dopiero co stworzone klasy do ViewModelu. Przechodzimy do niego i  tworzymy kolejne dwie właściwości i w konstruktorze przypisujemy do nich nowe obiekty:

public
ICommand Start { get; set; }
public ICommand Stop { get; set; }
public MainViewModel()
{
     Start = new StartCommand(this);
     Stop = new StopCommand(this);
}

Dokładnie do tych dwóch właściwości bindowaliśmy nasze przyciski poprzez Command.
Kolejnym krokiem ( obiecuje ze już nie wiele tego zostało ) jest utworzenie klasy która będzie nam losować liczby o danej długości jak i metoda o której wcześniej wspomniałem. Zaczniemy od klasy.
Tworzymy i nazywamy ja Losomat. Oto logika która jest w niej zawarta:

public static class Losomat
{
    private const int max = 999999999;
    private const int min = 100000000;
    private static readonly Random Random = new Random();

   
public static string Losuj(int ilosc)
    {
       return
       Random.Next(Convert.ToInt32(min / Math.Pow(10, 9 - ilosc)), Convert.ToInt32(max / Math.Pow(10, 9 - ilosc))).ToString();
    }
}

Mam nadzieje że wszytko w niej jest jasne.
Weźmiemy się teraz za metodę. Przechodzimy do ViewModelu i dodajemy.

public
void Losowanie(object param)
{
    for (int i = 0; i < SliderValue; i++)
    {
        Liczby += "0";
    }
    for (int i = SliderValue; i > 0; i--)
    {
        while (!StopButtonClicked)
        {
            var temp = Losomat.Losuj(i);
            Liczby = Liczby.Substring(0, SliderValue - i) + temp;
            System.Threading.Thread.Sleep(10);
        }
        StopButtonClicked = false;
     }
     CanStopButtonExecute = false;
}

Rzeczą która może się przydać z tego kawałka kodu może być System.Threading.Thread.Sleep. Jest to metoda która usypia dany watek na podana ilość czasu. Czas podajemy w milisekundach.
Ostatnia rzeczą jaka musimy wykonać jest złączenie Widoku z ViewModelem. Przechodzimy do Widoku, przed Gridem dodajemy przestrzeń nazw i definiujemy zasób dla naszego okna.

xmlns:Haslo="clr-namespace:Haslo"
<Window.Resources>
     <Haslo:MainViewModel x:Key="ViewModel"/>
</Window.Resources>

 Łączymy nasz nowy zasób z Gridem poprzez DataContext:

<Grid x:Name="LayoutRoot" DataContext="{StaticResource ViewModel }" >

W ostateczność nasz początek kodu Widoku powinien wyglądać następująco :
<Window
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:Haslo="clr-namespace:Haslo"
    x:Class="Haslo.MainWindow"
    x:Name="Window"
    Title="Haslo"
    Width="459" Height="204">    
    <Window.Resources>
           <Haslo:MainViewModel x:Key="ViewModel"/>
   </Window.Resources>
<Grid x:Name="LayoutRoot" DataContext="{StaticResource ViewModel }" >
Po tych wszystkich bojach możemy przystąpić do kompilacji naszego programu i cieszyć się jego możliwościami :D Kod z komentarzami będzie można pobrać z linku niżej.

Tagi: , ,

Windows Presentation Foundation

W środę na spotkaniu naszej grupy zaprezentowałem czym jest WPF, do czego może się przydać oraz pokazałem przykłady kodu.

Windows Presentation Foundation jest jednym z elementów wprowadzonych w .NET 3.0. Przede wszystkim umożliwia rozdzielenie kodu aplikacji od projektu interfejsu użytkownika. Dzięki narzędziu Microsoft Expression Blend Designer może projektować interfejs aplikacji w sposób dla niego wygodny (graficzne przeciąganie elementów, kolorowanie, ustawianie stanów animacji na timeline, itp).

W WPF interfejs jest definiowany w języku XAML. W tym języku możemy zadeklarować wygląd, animacje, sposób reagowania na zdarzenia. Dzięki temu aplikacja nie zawiera kodu odpowiedzialnego za np. animację interfejsu. Tym zajmuje się sam framework, a aplikacja zawiera tylko kod związany z logiką biznesową.

Kolejną zaletą WPF jest data binding. Możemy “zbindować” dowolną kontrolkę z jakimś obiektem, bazą danych, plikiem XML albo z inną kontrolką. W ten sposób każda zmiana w jednej z “zbindowanych” właściwości automatycznie będzie kopiowana do drugiej. Nie musimy sprawdzać czy coś się zmieniło  i samemu kopiować danych.

Jeśli chcemy w naszej aplikacji WPF mieć wstążkę (“ribbon”) znany z m.in. Microsoft Office 2007 wystarczy, że do aplikacji dołączymy bibliotekę RibbonControlsLibrary.dll.

Kolejnym krokiem jest zdefiniowanie komend. Aby komendy zawierały informacje potrzebne dla ribbona (ikony, etykiety…) muszą być typu RibbonCommand:

   1: <ResourceDictionary 
   2:     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   3:     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   4:     xmlns:r="clr-namespace:Microsoft.Windows.Controls.Ribbon;assembly=RibbonControlsLibrary">
   5:  
   6:     <r:RibbonCommand x:Key="CutCommand"
   7:         LabelTitle="Cut"
   8:         ToolTipTitle="Cut"
   9:         ToolTipDescription="Cut text element to the clipboard."
  10:         SmallImageSource="images/Cut.png"
  11:         LargeImageSource="images/Cut.png" />
  12:  
  13:     <r:RibbonCommand x:Key="CopyCommand"
  14:         LabelTitle="Copy"
  15:         ToolTipTitle="Copy"
  16:         ToolTipDescription="Copy text element to the clipboard."
  17:         SmallImageSource="images/Copy.png"
  18:         LargeImageSource="images/Copy.png" />
  19:  
  20: ...
  21:     
  22: </ResourceDictionary>

Wstążka pojawi się jeśli w oknie umieścimy kontrolkę Ribbon. Wstążka składa się z zakładek (RibbonTab), na każdej zakładce znajdują się grupy (RibbonGroup) i w tych grupach są przyciski i inne elementy. Poniżej przykład definicji ribbona:

   1: <r:Ribbon DockPanel.Dock="Top" Title="{Binding RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type Window}},Path=Title}">
   2:  
   3:           <r:RibbonTab Label="Banking">
   4:  
   5:               <!-- Define the groups in this tab -->
   6:               <r:RibbonTab.Groups>
   7:  
   8:                   <!-- Clipboard commands -->
   9:                   <r:RibbonGroup GroupSizeDefinitions="{StaticResource RibbonLayout}">
  10:                       <r:RibbonGroup.Command>
  11:                           <r:RibbonCommand LabelTitle="Clipboard" SmallImageSource="images/Paste.png" />
  12:                       </r:RibbonGroup.Command>
  13:                       <r:RibbonButton Command="me:AppCommands.Cut"/>
  14:                       <r:RibbonButton Command="me:AppCommands.Copy"/>
  15:                       <r:RibbonButton Command="me:AppCommands.Paste"/>
  16:                   </r:RibbonGroup>
  17:  
  18:                   <r:RibbonGroup GroupSizeDefinitions="{StaticResource RibbonLayout}">
  19:                       <r:RibbonGroup.Command>
  20:                           <r:RibbonCommand LabelTitle="Checkbook" SmallImageSource="images/AddNew.png" />
  21:                       </r:RibbonGroup.Command>
  22:                       <r:RibbonButton Command="me:AppCommands.AddNew"/>
  23:                       <r:RibbonButton Command="me:AppCommands.Clear" />
  24:                       <r:RibbonButton Command="me:AppCommands.Delete"/>
  25:                   </r:RibbonGroup>
  26:  
  27:                   <r:RibbonGroup GroupSizeDefinitions="{StaticResource RibbonLayout}">
  28:                       <r:RibbonGroup.Command>
  29:                           <r:RibbonCommand LabelTitle="Statements" SmallImageSource="images/Reconcile.png" />
  30:                       </r:RibbonGroup.Command>
  31:                       <r:RibbonButton Command="me:AppCommands.Reconcile"/>
  32:                   </r:RibbonGroup>
  33:  
  34:                   <r:RibbonGroup GroupSizeDefinitions="{StaticResource RibbonLayout}">
  35:                       <r:RibbonGroup.Command>
  36:                           <r:RibbonCommand LabelTitle="Online" SmallImageSource="images/CreditCards.png" />
  37:                       </r:RibbonGroup.Command>
  38:  
  39:                       <r:RibbonButton Command="me:AppCommands.DownloadStatements"/>
  40:                       <r:RibbonButton Command="me:AppCommands.DownloadCreditCards"/>
  41:                       <r:RibbonButton Command="me:AppCommands.Transfer"/>
  42:                   </r:RibbonGroup>
  43:  
  44:                   <r:RibbonGroup GroupSizeDefinitions="{StaticResource RibbonLayout}">
  45:                       <r:RibbonGroup.Command>
  46:                           <r:RibbonCommand LabelTitle="Tools" SmallImageSource="images/Backup.png" />
  47:                       </r:RibbonGroup.Command>
  48:  
  49:                       <r:RibbonButton Command="me:AppCommands.Backup"/>
  50:                       <r:RibbonButton Command="me:AppCommands.Calculator"/>
  51:                   </r:RibbonGroup>
  52:  
  53:               </r:RibbonTab.Groups>
  54:           </r:RibbonTab>
  55:  
  56:           <r:RibbonTab Label="Reporting">
  57:               <r:RibbonTab.Groups>
  58:                   <r:RibbonGroup GroupSizeDefinitions="{StaticResource RibbonLayout}">
  59:                       <r:RibbonGroup.Command>
  60:                           <r:RibbonCommand LabelTitle="Reports" SmallImageSource="images/CashflowReport.png" />
  61:                       </r:RibbonGroup.Command>
  62:  
  63:                       <r:RibbonButton Command="me:AppCommands.TrendReport" />
  64:                       <r:RibbonButton Command="me:AppCommands.BudgetReport"/>
  65:                       <r:RibbonDropDownButton Command="me:AppCommands.OtherReports">
  66:                           <MenuItem Header="Cash Flow Report" />
  67:                           <MenuItem Header="Favorites Report" />
  68:                           <MenuItem Header="Spending Report" />
  69:                           <MenuItem Header="Savings Report" />
  70:                           <MenuItem Header="Credit Report" />
  71:                       </r:RibbonDropDownButton>
  72:  
  73:                   </r:RibbonGroup>
  74:               </r:RibbonTab.Groups>
  75:           </r:RibbonTab>
  76:  
  77:       </r:Ribbon>

Jeszcze nie wygląda to tak jakbyśmy sie tego spodziewali, brakuje trzech rzeczy:

  1. Menu aplikacji.
    109-5[1] 
    Menu aplikacji tworzymy ustawiając właściwość Ribbon.ApplicationMenu. W menu możemy wstawić komendy nowe (jak w poniższym przykładzie) jak i te same które zdefiniowaliśmy wcześniej.

       1: <r:Ribbon.ApplicationMenu>
       2:                 <r:RibbonApplicationMenu>
       3:                     <r:RibbonApplicationMenu.Command>
       4:                         <r:RibbonCommand 
       5:                             Executed="OnCloseApplication"
       6:                             LabelTitle="Application Button"
       7:                             LabelDescription="Close the application."
       8:                             SmallImageSource="images/Coins.png"
       9:                             LargeImageSource="images/Coins.png"
      10:                             ToolTipTitle="Personal Finance Manager"
      11:                             ToolTipDescription="Click here to open or save a checkbook register." />
      12:                     </r:RibbonApplicationMenu.Command>
      13:                     <r:RibbonApplicationMenuItem>
      14:                         <r:RibbonApplicationMenuItem.Command>
      15:                             <r:RibbonCommand 
      16:                                 LabelTitle="_Close" 
      17:                                 LabelDescription="Close the Application"
      18:                                 Executed="OnCloseApplication" />
      19:                         </r:RibbonApplicationMenuItem.Command>
      20:                     </r:RibbonApplicationMenuItem>
      21:                 </r:RibbonApplicationMenu>
      22:             </r:Ribbon.ApplicationMenu>
  2. QuickAccessToolBar
    109-6[1] 
    QuickAccessToolBar to pasek przycisków z najczęściej używanymi komendami, definiowany przez użytkownika

       1: <r:Ribbon.QuickAccessToolBar>
       2:     <r:RibbonQuickAccessToolBar CanUserCustomize="True">
       3:         <r:RibbonButton Command="me:AppCommands.AddNew" r:RibbonQuickAccessToolBar.Placement="InCustomizeMenuAndToolBar" />
       4:         <r:RibbonButton Command="me:AppCommands.Cut" r:RibbonQuickAccessToolBar.Placement="InCustomizeMenuAndToolBar" />
       5:         <r:RibbonButton Command="me:AppCommands.Copy" r:RibbonQuickAccessToolBar.Placement="InCustomizeMenuAndToolBar" />
       6:         <r:RibbonButton Command="me:AppCommands.Paste"  r:RibbonQuickAccessToolBar.Placement="InCustomizeMenuAndToolBar" />
       7:         <r:RibbonButton Command="me:AppCommands.Help" r:RibbonQuickAccessToolBar.Placement="InToolBar" />
       8:     </r:RibbonQuickAccessToolBar>
       9: </r:Ribbon.QuickAccessToolBar>
  3. Office 2007 Theme Resources
    Aby w naszej aplikacji przycisk uruchamiający menu aplikacji był okrągły i umieszczony w lewym górnym rogu okna musimy zmienić klasę z której dziedziczy nasze okno z Window na RibbonWindow, oraz dodać do aplikacji ustawienia wyglądu z kontrolki :

       1: <Window.Resources>
       2:     <ResourceDictionary>
       3:         <ResourceDictionary.MergedDictionaries>
       4:             <ResourceDictionary 
       5:                 Source="/RibbonControlsLibrary;component/Themes/Office2007Blue.xaml"/>
       6:         </ResourceDictionary.MergedDictionaries>
       7:     </ResourceDictionary>
       8: </Window.Resources>

Samą bibliotekę z ribbonem możemy ściągnąć po zaakceptowaniu licencji na stronie  Office UI Licensing (link License the Office UI). Ribbona możemy używać zupełnie bezpłatnie, wystarczy, że nasza aplikacja nie będzie konkurencyjną dla Microsoft Office (i zastosujemy się do zaleceń z licencji dotyczących wyglądu samego ribbona).

Jeśli ktoś chce sie pobawić WPFem to zamieszczam kilka linków z ciekawymi przykładami:

http://windowsclient.net/downloads/folders/wpfsamples/default.aspx

http://blog.trivadis.com/blogs/manuelmeyer/archive/2008/06/12/cool-wpf-samples.aspx

http://www.actiprosoftware.com/Support/ResourceGuides/WPF/ViewCategory.aspx?ResourceGuideCategoryID=6

Tagi: , , ,

WPF Webbrowser, Frame i WebBrowser w WinForms - porównanie

Chcąc wczytać stronę www do naszej aplikacji w WPF'ie mamy trzy możliwości (pod warunkiem posiadania .net 3.5 SP1)

  1. wykorzystać kontrolkę WebBrowser (System.Windows.Controls.WebBrowser)
  2. wykorzystać kontrolkę Frame (System.Windows.Controls.Frame)
  3. oraz skorzystać w kontrolki WebBrowser z winforms (System.Windows.Forms.WebBrowser)

Dzisiejszy wpis będzie porównaniem tych trzech sposobów w kontekście pobierania i przetworznia kodu wczytanej strony.

Zacznijmy od stworzenia projektu (lub ściągnięcia gotowego kodu) gdzie dodajemy Grida z trzema kolumnami, w których kolejno będą kontrolki z punktów 1,2,3. Z dodaniem kontrolek WebBrowser i Frame z WPF'a nie powinno być żadnych problemów (przeciągamy je z ToolBox'a), lecz aby dodać WebBrowser z WinForms na początku musimy dodać dwie referencje do naszego projektu:System.Windows.Forms i WindowsFormsIntegration a następnie kontrolkę WindowsFormsHost w której to umieścimy WebBrowser'a z WinForms.

 

   1: <Grid>
   2:        <Grid.ColumnDefinitions>
   3:            <ColumnDefinition Width="*"></ColumnDefinition>
   4:            <ColumnDefinition Width="*"></ColumnDefinition>
   5:            <ColumnDefinition Width="*"></ColumnDefinition>
   6:        </Grid.ColumnDefinitions>
   7:        <Grid.RowDefinitions>
   8:            <RowDefinition Height="*"></RowDefinition>
   9:        </Grid.RowDefinitions>
  10:        
  11:        <DockPanel Grid.Column="0" Grid.Row="0" >
  12:            <TextBlock x:Name="tbWpfWBDesc" DockPanel.Dock="Top" Height="25" Margin="2" FontWeight="Bold" HorizontalAlignment="Center">Wpf WebBrowser Control</TextBlock>
  13:            <Button x:Name="btnWBPageLoad" DockPanel.Dock="Bottom">New Page</Button>
  14:            <WebBrowser x:Name="wbPage" ></WebBrowser>
  15:            
  16:        </DockPanel>
  17:        
  18:        <DockPanel Grid.Column="1" Grid.Row="0">
  19:            <TextBlock x:Name="tbFrameDesc" DockPanel.Dock="Top" Height="25"  Margin="2" FontWeight="Bold" HorizontalAlignment="Center">Wpf Frame Control</TextBlock>
  20:           <Button x:Name="btnFrmPageLoad" DockPanel.Dock="Bottom" >New Page</Button> 
  21:            <Frame x:Name="frmPage"></Frame>
  22:           
  23:        </DockPanel>
  24:        
  25:        <DockPanel Grid.Column="2" Grid.Row="0">
  26:            <TextBlock x:Name="tbWinWBDesc" DockPanel.Dock="Top" Height="25" Margin="2"  FontWeight="Bold" HorizontalAlignment="Center">Wpf Frame Control</TextBlock>
  27:            <Button x:Name="btnWinFormPageLoad" DockPanel.Dock="Bottom">New Page</Button>
  28:            <WindowsFormsHost>
  29:                <wf:WebBrowser x:Name="wbWinFormPage" />
  30:                
  31:            </WindowsFormsHost>
  32:            
  33:        </DockPanel>
  34:    </Grid>

Mamy już nasz widok, teraz aby wczytać stronę do naszych kontrolek wystarczy wywołać metodę Navigate z adresem strony (każda kontrolka ma trochę inną sygnaturę tej metody, lecz wszystkie udostępniają przeciążoną wersję  Navigate(Uri source) ). Wywołajmy ją zaraz po załadowaniu okna wczytując losową stronę z wikipedi (http://pl.wikipedia.org/wiki/special:random), gdy uruchomimy nasz projekt zobaczymy że załadują się trzy różne strony dodatkowo warto zauważyć że możemy przechodzić do innych stron klikając linki. Do tej pory różnic raczej nie zauważymy (prócz innych pasków przewijania w ostatniej kontrolce) i jeżeli w waszej aplikacji zależy wam tylko na prezentacji danej strony to wszystkie kontrolki są dobre. Warto także zauważyć że wszystkie obsługują historię przeglądanych stron.
Przejdźmy teraz do meritum, czyli po załadowaniu strony chcemy pobrać jej kod i tutaj pojawiają się już znaczące różnice.

WebBrowser z WPF

Pozwala na wyświetlanie strony www, wykonuje kod javascript oraz obsługuje flash’a jeżeli jest zainstalowany. Warto zwrócić na kilka eventów do których możemy się podpiąć:

  • Navigating – pozwala na podjęcie jakiejś akcji przed żądaniem strony, pomocna przy anulowaniu żądania (ustawiająć e.Cancel=true), parametr typu NavigatingCancelEventArgs dostarczy nam podstawowych informacji o żądaniu, lecz co się okazuje jedna z ważniejszych właściwości WebRequest jest ustawiana na NULL :)

NavigatinCancelArgs 

  • Navigated – nawiązano połączenie, rozpoczęto ściąganie dokumentu, może nie być jeszcze w całości
  • LoadCompleted – ściąganie dokumentu już się zakończyło, powinien być w całości (w praktyce korzystać z tego zdarzenia)
  • Dwa kolejne zdarzenia wywoływane po nawiązaniu już połączenia z serwerem, obie w parametrze dostają obiekt NavigationEventArgs. Po przyjrzeniu się jego właściwościom ucieszymy się na widok WebResponse, z którego możemy pobrać strumień i go odczytać (będziemy mieli upragniony kod strony) np. takim kodem

       1: if(e.WebResponse!=null)
       2: {
       3:     //always false
       4:     var response = e.WebResponse;
       5:     var responseStream = (Stream)response.GetResponseStream();
       6:     var readStream = new StreamReader(responseStream, true);
       7:  
       8:  
       9:     //absolut URI 
      10:     var address = response.ResponseUri.AbsoluteUri;
      11:     var webContent = readStream.ReadToEnd();
      12: }

    lecz życie byłoby zbyt piękne gdyż okazuje się że on także jest ustawiany na NULL

    NavigationEventArgs

    co nam zostaje, a może sama kontrolka ma właściwość z której pobierzemy kod? Otóż okazuje się że tak jest to właściwość Document, a więc do dzieła, trzeba dodać referencję do Microsoft.mshtml

       1: mshtml.HTMLDocument document = wbPage.Document as mshtml.HTMLDocument;
       2: string pageHtml = document.documentElement.outerHTML;

    No pięknie jesteśmy w domu mamy pobrany kod HTML. Przyjrzyjmy mu się bliżej, pobraliśmy stronę z wikipedii zgodną z XHTML a tutaj dostajemy HTML’a bez zamkniętych tagów bez DocType itp. jednak nie o to nam chodziło. Kod jest automatycznie modyfikowany i transformowany właśnie do HTML.

       1: <HTML dir=ltr lang=pl xml:lang="pl" xmlns="http://www.w3.org/1999/xhtml"><HEAD><TITLE>Akademia Świętego Łukasza – Wikipedia, wolna encyklopedia</TITLE>
       2: <META content="text/html; charset=utf-8" http-equiv=Content-Type>
       3: <META content=text/css http-equiv=Content-Style-Type>
       4: <META name=generator content="MediaWiki 1.16alpha-wmf">
       5: <META name=keywords content="Akademia Świętego Łukasza,1577,Architekt,Cech,Europa,Haft,Malarstwo,Papież Grzegorz XIII,Rzeźba,Rzym,Wł."><LINK title=Edytuj rel=alternate type=application/x-wiki href="/w/index.php?title=Akademia_%C5%9Awi%C4%99tego_%C5%81ukasza&amp;action=edit">
       6: ....

    Wobec powyższego powstaje pytanie, czy istnieje możliwość pobrania kodu załadowanej strony z kontrolki WebBrowser z WPF’a? Ja nie znalazłem jak to można zrobić :)

    Frame

    Pozwala na wczytywanie stron www oraz WPF Pages. Posiada eventy podobne do WebBrowser, mają one także podobną sygnaturę

    • Navigating
    • Navigated
    • LoadCompleted

    Lecz tym razem obiekty WebRequest i WebResponse w zdarzeniach są ustawiane, hura :)

    frmNavigationEventArgs 

    Zabieramy się do pobierania kodu strony, czytając z strumienia korzystając z kodu

       1: if (e.WebResponse != null)
       2: {
       3:     var response = e.WebResponse;
       4:     var responseStream = (Stream)response.GetResponseStream();
       5:  
       6:     //uwaga wyrzuca wyjątek
       7:     //throw ArgumentException Stream was not readable.
       8:     var readStream = new StreamReader(responseStream, true);
       9:     string webContent = readStream.ReadToEnd();
      10:  
      11:     var address = response.ResponseUri.AbsoluteUri;
      12:  
      13: }

    lecz dostaniemy wyjątek “Stream was not readable” czyli nie odczytamy kodu strony. Poza tym pojawia się drugi problem iż zdarzenia (Navigating, Navigated, LoadCompleted) są wywoływane tylko za pierwszym razem, gdy klikamy w linki i przechodzimy do nowych stron żaden handler nie jest wywoływany. Gdy jawnie wywołamy metodę Navigate to handlery zadziałają lecz obiekt WebResponse będzie równy Null i strona w kontrolce się nie zmieni pomaga wywołanie metody Refresh. Wobec powyższych i ta kontrolka nie pozwala na pobranie kodu załadowanej strony.

    WebBrowser from WinForms

    Działa podobnie jak WebBrowser z WPF’a pozwala załadowanie strony, wykonuje javascript oraz flash’a. Nas będą interesowały zdarzenia:

    • Navigating
    • Navigated
    • DocumentCompleted

    Zdarzenia wywoływane są w podobnych okolicznościach co w powyższych przykładach, lecz dostają inne obiekty odpowiednio WebBrowserNavigatingEventArgs, WebBrowserNavigatedEventArgs, WebBrowserDocumentCompletedEventArgs. Zbadajmy ostatnie zdarzenie i parametry jakie dostaje

    winDocumentCompletedEventArgs

    niestety nie ma obiektu WebResponse tylko sam Url strony którą pobrano. Ale nie załamujmy się otóż okazuje się że WebBrowser z WinForms ma jedną cenną właściowść DocumentText, która zwraca tekst pobranej strony (bez żadnych modyfikacji) aby go pobrać wystarczy

       1: void wbWinFormPage_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
       2: {
       3:     string webContent = wbWinFormPage.DocumentText;
       4: }

    Przy korzystaniu z tej kontrolki zauważyłem dodatkowo że każdy handler wywoływany jest dwa razy. Pierwszy z Url’em == “auto:blank” a drugi z tym właściwym, lecz jakoś mi to nie przeszkadzało.

    Podsumowanie

    Chcąc dać użytkownikowi możliwość przeglądania stron www w naszej aplikacji możemy zastosować te trzy kontrolki, lecz jeżeli chcemy mieć dostęp do źródła strony, wiedzieć kiedy nastąpiło żądanie nowej strony pod jaki adres kieruje się użytkownik to zdecydowanie polecam WebBrowser z WinForms.

    Ściągnij przykład

    Tagi: , ,

    Zapamiętywanie pozycji oraz rozmiaru okna w WPFie

    Po wydaniu kolejnej wersji BlipFace jeden z użytkowników napisał, że przydało by się aby BlipFace zapamiętywał położenie oraz rozmiar okna między uruchomieniami.  Po chwili szukania znalazłem post Erwyna van der Meera na temat Remembering window positions in WPF. Niestety zaprezentowane rozwiązanie zapamiętywało rozmiar oraz położenie jednego okna. Długo nie myśląc zmodyfikowałem rozwiązanie aby spełniało moje wymagania.

    Aby z niego skorzystać trzeba dodać dwie rzeczy do projektu:

    • do kodu xaml definicji interesującego nas okna trzeba dodać dwie linijki:
    xmlns:s="clr-namespace:SaveWindowsPositionTest"
    s:SaveWindowsPosition.Save="True"
    • oraz dodać ustawienie do projektu o nazwie klasy naszego okna jak poniżej:

    wpfzapamietywanieokna1

    I możemy już się cieszyć zapamiętywaniem położenia i rozmiaru okna Windows1. No dobra ale jak to wszystko działa? Kod klasy, która za wszystko odpowiada:

       1: public class SaveWindowsPosition
       2:     {
       3:         #region Constructor
       4:         private Window window = null;
       5:  
       6:         public SaveWindowsPosition(Window window)
       7:         {
       8:             this.window = window;
       9:         }
      10:  
      11:         #endregion
      12:  
      13:         #region Attached "Save" Property Implementation
      14:         /// <summary>
      15:         /// Register the "Save" attached property and the "OnSaveInvalidated" callback 
      16:         /// </summary>
      17:         public static readonly DependencyProperty SaveProperty
      18:            = DependencyProperty.RegisterAttached("Save", typeof(bool), typeof(SaveWindowsPosition),
      19:                 new FrameworkPropertyMetadata(new PropertyChangedCallback(OnSaveInvalidated)));
      20:  
      21:         public static void SetSave(DependencyObject dependencyObject, bool enabled)
      22:         {
      23:             dependencyObject.SetValue(SaveProperty, enabled);
      24:         }
      25:  
      26:         /// <summary>
      27:         /// Called when Save is changed on an object.
      28:         /// </summary>
      29:         private static void OnSaveInvalidated(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
      30:         {
      31:             Window window = dependencyObject as Window;
      32:             if (window != null)
      33:             {
      34:                 if ((bool)e.NewValue)
      35:                 {
      36:                     SaveWindowsPosition settings = new SaveWindowsPosition(window);
      37:                     settings.Attach();
      38:                 }
      39:             }
      40:         }
      41:  
      42:         #endregion
      43:  
      44:         #region Protected Methods
      45:         /// <summary>
      46:         /// Load the Window Size Location and State from the settings object
      47:         /// </summary>
      48:         protected virtual void LoadWindowState()
      49:         {
      50:             Properties.Settings.Default.Reload();
      51:             Rect rect = Rect.Parse(Properties.Settings.Default[window.GetType().Name].ToString().Replace(';', ','));
      52:  
      53:             if (rect != Rect.Empty)
      54:             {
      55:                 this.window.Left = rect.Left;
      56:                 this.window.Top = rect.Top;
      57:                 this.window.Width = rect.Width;
      58:                 this.window.Height = rect.Height;
      59:             }
      60:         }
      61:  
      62:         /// <summary>
      63:         /// Save the Window Size, Location and State to the settings object
      64:         /// </summary>
      65:         protected virtual void SaveWindowState()
      66:         {
      67:             Properties.Settings.Default[window.GetType().Name] = this.window.RestoreBounds.ToString();
      68:             Properties.Settings.Default.Save();
      69:         }
      70:         #endregion
      71:  
      72:         #region Private Methods
      73:  
      74:         private void Attach()
      75:         {
      76:             if (this.window != null)
      77:             {
      78:                 this.window.Closing += new CancelEventHandler(window_Closing);
      79:                 this.window.Initialized += new EventHandler(window_Initialized);
      80:             }
      81:         }
      82:  
      83:         private void window_Initialized(object sender, EventArgs e)
      84:         {
      85:             LoadWindowState();
      86:         }
      87:  
      88:         private void window_Closing(object sender, CancelEventArgs e)
      89:         {
      90:             SaveWindowState();
      91:         }
      92:         #endregion
      93:     }

    A teraz kilka słów o klasie SaveWindowsPosition:

    • w regionie Attached "Save" Property Implementation następuje rejestracja właściwości Save (tą, do której w xamlu okna przekazujemy wartość true) oraz callbacku OnSaveInvalidated, w którym podpinamy się pod zdarzenia Closing oraz Initialized okna, w których zapisujemy oraz ustawiamy położenie oraz rozmiar okna
    • metody LoadWindowState odczytuje ustawiania oraz SaveWindowState je zapisuje w pliku app.config. Założyłem sobie, że ustawiania mają taką samą nazwę jak nazwa klasy okna (window.GetType().Name) ale bardzo łatwo można to zmienić i uwzględniać np. nazwę okna (window.Name).

    Demo przestawiające użycie powyższego rozwiązania - SaveWindowsPositionTest.zip (54,51 kb)

    Tagi: , , ,

    Ikona aplikacji w tray’u – WPF NotifyIcon

    Ostatnio Krzyśkowi Sopyła (^ksirg) zacząłem pomagać w pracy nad klientem (BlipFace) napisanym w WPFie do Blipa. Jedną z pierwszych rzeczy za jakie się zabrałem (której między innymi mi w BlipFace brakowało) było dodanie do ikony BlipFace w tray’u menu kontekstowego oraz wyświetlanie ładnych powiadomień o przyjściu nowych statusów. Kontrolka (System.Windows.Forms.NotifyIcon), z której korzystał Krzysiek niestety za bardzo się nie nadawała (szczególnie do tego drugiego). Dlatego skorzystałem z jedynie słusznej wyszukiwarki i znalazłem coś fajnego, co spełnia jak na razie moje wymagania - WPF NotifyIcon.

    W poniższym wpisie pokaże jak skorzystać z tej kontrolki do implementacji podstawowych funkcjonalności jaką powinien mieć każdy program. Jaka to funkcjonalność?

    • wyświetlanie ikony aplikacji w tray’u
    • kliknięcie lewym przyciskiem myszy w ikonę w tray’u powoduje pokazanie głównego okna aplikacji (drugie kliknięci minimalizuje okno)
    • kliknięcie prawym klawiszem powoduje wyświetlenie się menu kontekstowego z takimi akcjami jak: zamknięcie programu, pokazanie okna z informacjami o programie, pokazanie okna ustawień aplikacji, pokazanie okna głównego aplikacji
    • możliwość wyświetlenie balonu w okolicach tray’a z informacją

    A więc przejdźmy do sedna, czyli jak to wszystko zrobić. Po pierwsze musimy ściągnąć bibliotekę z kontrolką. Następnie tworzymy nowy projekt WPF Application i dodajemy do niego referencje do ściągniętej biblioteki (Hardcodet.Wpf.TaskbarNotification.dll). Kolejno modyfikujemy kod xaml okna głównego aplikacji, coś na wzór kodu poniżej:

       1: <Window x:Class="WpfNotifyIcon.MainWindow"
       2:     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
       3:     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
       4:     xmlns:tb="http://www.hardcodet.net/taskbar"
       5:     Title="MainWindow" Height="300" Width="300" Closing="Window_Closing">
       6:     <Grid>
       7:         <Label Content="Okno główne"></Label>
       8:         <Button Content="Pokaż ballon" Height="50" Width="100" Click="Button_Click"></Button>
       9:         <tb:TaskbarIcon                
      10:             Name="NotifyIcon"
      11:             IconSource="/Icons/Computers.ico"
      12:             ToolTipText="Kliknij aby pokazać okno aplikacji" 
      13:                 TrayLeftMouseUp="taskbarIcon_TrayLeftMouseUp"
      14:                 >
      15:             <tb:TaskbarIcon.ContextMenu>
      16:                 <ContextMenu>
      17:                     <MenuItem Name="ShowWindowsMenuItem" Header="Pokaż" Click="ShowWindowsMenuItem_Click"/>
      18:                     <MenuItem Name="ShowSettingsWindowsMenuItem" Header="Ustawienia" Click="ShowSettingsWindowsMenuItem_Click"/>
      19:                     <MenuItem Name="ShowAboutWindowsMenuItem" Header="O Programie" Click="ShowAboutWindowsMenuItem_Click"/>
      20:                     <MenuItem Name="CloseMenuItem" Header="Zamknij" Click="CloseMenuItem_Click"/>
      21:                 </ContextMenu>
      22:             </tb:TaskbarIcon.ContextMenu>
      23:         </tb:TaskbarIcon>
      24:     </Grid>
      25: </Window>

    a tak wygląda kod behind:

       1: private void taskbarIcon_TrayLeftMouseUp(object sender, RoutedEventArgs e)
       2: {
       3:     if (WindowState == WindowState.Minimized)
       4:         WindowState = WindowState.Normal;
       5:     else
       6:         WindowState = WindowState.Minimized;
       7: }
       8:  
       9: private void ShowWindowsMenuItem_Click(object sender, RoutedEventArgs e)
      10: {
      11:     WindowState = WindowState.Normal;
      12: }
      13:  
      14: private void ShowSettingsWindowsMenuItem_Click(object sender, RoutedEventArgs e)
      15: {
      16:     (new SettingsWindow()).Show();
      17: }
      18:  
      19: private void ShowAboutWindowsMenuItem_Click(object sender, RoutedEventArgs e)
      20: {
      21:     (new AboutWindow()).Show();
      22: }
      23:  
      24: private void CloseMenuItem_Click(object sender, RoutedEventArgs e)
      25: {
      26:     Close();
      27: }
      28:  
      29: private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
      30: {
      31:     NotifyIcon.Dispose();
      32: }
      33:  
      34: private void Button_Click(object sender, RoutedEventArgs e)
      35: {
      36:     Ballon ballon = new Ballon();
      37:     NotifyIcon.ShowCustomBalloon(ballon, PopupAnimation.Slide, 3000);
      38: }

    A teraz kilka słów wyjaśnienia:

    • aby móc skorzystać z kontrolki musimy do kodu xaml dodać odpowiednią przestrzeń nazw – linijka 4 - xmlns:tb=http://www.hardcodet.net/taskbar
    • od linijki 9 do linijki 23 znajduje się definicja kontrolki TaskbarIcon, czyli naszej ikonki w tray’u
    • atrybuty kontrolki TaskbarIcon w większości są chyba jasne, jedynie może wymagać wyjaśnienia linijka 13 (TrayLeftMouseUp="taskbarIcon_TrayLeftMouseUp"), dzięki niej podpinamy zdarzenie, w którym oprogramowujemy minimalizowanie i powrót do normalnego wyglądu okna aplikacji po kliknięciu na ikonę w tray’u (linijki 1-7 kodu behind)
    • w linijkach 15-22 znajduje się definicja menu kontekstowego składającego się z czterech pozycji, do których są podpięte odpowiednie zdarzenia kliknięcie
    • ważna jest jeszcze linijka 5 pliku xaml, a dokładniej ustawienie zdarzenia Closing okna, w nim musimy wywołać metodę Dispose() TaskbarIcon, w innym przypadku ikona aplikacji nie zniknie z tray’u

    Jak zapewne zauważyliście w okno wrzuciłem przycisk, który powoduje pojawienie się balonu w okolicach tray’a. Co jest fajne (jak dla mnie) to tym balonem może być dowolnie zdefiniowane kontrolka użytkownika, na przykład napisane w WPFie (dokładnie każda klasa dziedzicząca po System.Windows.UIElement), więc mamy duże pole do popisu :)

     

    Oczywiście powyższe funkcjonalności to wierzchołek góry lodowej jaki umożliwia ta biblioteka. Polecam przestudiowanie dostarczonych przykładów wraz z samą biblioteką.

    Kod prostego dema: WpfNotifyIcon.zip (100,17 kb)

    Tagi: , , ,

    BlipFace klient do blip'a


    Ponad miesiąc temu pisałem o blipface'ie, desktopowym kliencie do sewrisu blip.pl. Projekt cały czas się rozwija raz szybciej raz wolniej, lecz cały czas do przodu. Wpis ten będzie raczej podusmowaniem co udało nam się zrobić od tamtej pory i jednocześnie zachętą do ściągnięcia kodu (tak, tak BlipFace jest OpenSource od samego początku)

    Po krótce co udało nam się zrobić przez ostatni miesiąc:

    • BlipFace ma bloga na którym powiadamiamy o nowych wydaniach 
    • stworzyliśmy także miejsce do zgłaszania błędów i uwag
    • Dodaliśmy funkcjonalności: logowanie, zapisywanie loginu i hasła, możliwość cytowania, odpowiadania, wyświetlanie zdjęć, klikalne linki, minimalizowanie do tray'a 

    Teraz do rzeczy co zrobić aby uruchomić aplikację, potrzebne nam będą:

    Gdy mamy już wszystko zainstalowane, ściągamy kod z serwisu github.com klikając pawym przyciskiem myszy na folderze, w którym chcemy mieć projekt wybieramy GitExtension->Clone a w okienku wpisujemy git://github.com/ksirg/blipface.git całość powinna wyglądać tak:
      

     
     Po uruchomieniu Visual Studio zobaczymy trzy projekty:
    1. BlipFace - główna aplikacja, widok w WPF'ie. W programie zaimplementowałem własną wersję MVP opratą na UserControls
    2. BlipFace.Service - biblioteka opakowująca WCF REST do komunikacji z blipem, można wykorzystać w swoich innych projektach
    3. BlipFace.Install - projekt instalatora
    Zachęcam do zabawy z Debuggerem warto zacząć od klasy HostWindow.xaml.cs od niej wszystko się zaczyna. Dodam iż w folderze projektu znajduje się plik Enterprise Architect z diagramami klas oraz komentarzami. 
     
    Obecnie BlipFace wygląda tak:
     

    Tagi: , ,

    BlipFace kolejny klient do blip'a

    Chciałbym podzielić się z wami informacjami o projekcie, który wraz z Marcinem Czerpakiem zaczęliśmy stosunkowo niedawno. Jest to klient do serwisu http://blip.pl. Otóż od dłuższego czasu używałem blip'a przez GG lecz denerwowały mnie dwie rzeczy:

    1. Nie mogłem oglądać obrazków w statusach - trzeba było wejść na stronę
    2. Trudności z odpowiadaniem i cytowaniem użytkownika
    Wobec powyższych postanowiliśmy zrobić swoją własną aplikację, nazwa kodowa BlipFace :)
     
    Narazie jesteśmy jeszcze w fazie alfa, lecz część funkcjonaloności już jest i działa. W chwili obecnej blipface wygląda tak:
     

     
     
     Funkcjonalności które działają:
    • Pobieranie dashboardu użytkownika
    • Dodawanie wpisów
    Należy zaznaczyć że brakuje jeszcze logowania - narazie na sztywno w kodzie jest wpisany login i hasło użytkownika: blipface :) 
    Cały projekt ma udostępniony kod źródłowy na serwisie GitHub - http://github.com/ksirg/blipface/tree/master
     Aby go uruchomić należy ściągnąć kod, uruchomić plik BlipFace.sln w visual studio 2008, ewentualnie podmnieć login i hasło na swoje w kilku plikach.
     
    Czekamy na wasze uwagi,błędy, sugestie, mogą one być wyrażone w formie komentarzy pod wpisem lub na blipie (^ksirg , ^cnk , ^blipface)

    Tagi: ,

    Eastgroup.pl na facebooku