sobota, 26 grudnia 2015

Wielowątkowość - Task

Dziś porozmawiamy o wątkach w C#

Klasa Task wymaga zastosowania następującej przestrzeni nazw:

using System.Threading.Tasks;

Jest kilka sposobów tworzenia wątków.. Stwórzmy do tego prostą metodę w Console Aplication:

public static int DoWork( int val )
{
    var tmpVal = 0;
    for ( int i = 0; i < val; i++ ) {
         tmpVal += i;
    }
    return tmpVal;
}

static void Main( string[] args )
{
    //..
}

Przypominam że delegat Action umożliwia przypisanie metody zwracającej void.


1) Pierwszy ze sposobów to Lambda expression :

static void Main( string[] args )
{
     const int taskLoop = 100;
     var task = new Task<int>( () => DoWork( taskLoop ) );
     task.Start();
     Console.WriteLine( task.Result );
}

2) Lub..

static void Main( string[] args )
{
     const int taskLoop = 100;
     var task = new Task<int>( n => DoWork( (int)n ), taskLoop );
     task.Start();
     Console.WriteLine(task.Result);
}

3) Można również zastosować metodę "w locie" umieszczając ją w nawiasach:

static void Main( string[] args )
{
     const int taskLoop = 100;
     var task = new Task<int>( () => {
     var tmpVal = 0;
     for ( int i = 0; i < taskLoop; i++ ) {
         tmpVal += i;
     }
     return tmpVal;
     });
     task.Start();
     Console.WriteLine( task.Result );
}



Ważnym aspektem wątków jest możliwość ich przerwania w czasie wykonywania z poziomu wątku głównego można to powiązać z wyrzuceniem wyjątku:

public static int DoWork(CancellationToken ct,  int val )
{
    var tmpVal = 0;
    for ( int i = 0; i < val; i++ ) {
        tmpVal += i;
        ct.ThrowIfCancellationRequested();
    }
    return tmpVal;
}

static void Main( string[] args )
{
      const int taskLoop = 100;
      CancellationTokenSource cs = new CancellationTokenSource();
      var task = new Task<int>(() => DoWork(cs.Token,1000));
      task.Start();
      cs.Cancel();

      try {
           Console.WriteLine("Wynik to: {0}",task.Result);
      }
      catch ( AggregateException ex ) {
      ex.Handle( e => e is OperationCanceledException );
      Console.WriteLine("Operacja anulowana");
}

Powyższy kod spowoduje zatrzymanie wykonywania wątku już przy pierwszej pętli a to wszytko poprzez argument CancellationTokenSource cs = new CancellationTokenSource(); który po wywołaniu metody Close() powoduje wyrzucenie wyjątku.

 

piątek, 11 grudnia 2015

Użyteczne Rozszerzenia dla VB

Poniżej przedstawiam Rozszerzenia dla VB:

Rozszerzenia:
Tools > Extension Manager





Viasfora - Roszerzenie do VB mające na celu zwiększenie przejrzystości kodu poprzez podstawienie kolorowych nawiasów.  http://viasfora.com/#/texted






Użyteczne NuGety .NET

Poniżej przedstawiam zestaw użytecznych NuGetów Visual Basic:

NuGety:
Prawy przycisk na projekcie > Manage NuGet Packages



NuGet z Konsoli:

1. Prawy przycisk myszy na projekt > Package Manager Console
2. Tools > NuGet Package Manager > Package Manager Console






Faker  - baza losowych danych osobowych - przydatna podczas tworzenia testowej bazy danych.  https://www.nuget.org/packages/Faker.Net/

PM> Install-Package Faker.Net



TopShelf  - zestaw bibliotek do tworzenia Windows Service http://topshelf-project.com/



HTML Agility Pack - library do parsowania stron www. https://htmlagilitypack.codeplex.com/


MahApps.Metro 1.1.2 

- library do skórek WPF  

https://www.nuget.org/packages/MahApps.Metro/
PM> Install-Package MahApps.Metro



Quartz.NET - biblioteki do obsługi czynności wg. ustalonego przez użytkownika czasu
http://www.quartz-scheduler.net/


Route Debugger - narzędzie to sprawdzania wywołanych ścieżek routingu. https://www.nuget.org/packages/routedebugger/



MongoDb - biblioteka zarządzania nierelacyjna baza danych. https://www.mongodb.org/


Fody - bdzieki tej bibliotece działania są wykonywane dopiero, gdy zostanie zainstalowany jakikolwiek z pluginów:

[ImplementPropertyChanged]
public class Person 
{        
    public string value1 { get; set; }
    public string value2 { get; set; }
 
    public string GetVal1
    {
        get
        {
            //..get
        }
    }
}


sobota, 31 października 2015

XmlSerializer - Cz2. Serializacja, deserializacja - uzupełnienie

Do części 1 chciałbym dodać jeszcze kilka modyfikacji i opisów. Mianowicie chciałbym zamiast listy dołożyć dodatkową klasę do Serializacji i deserializacji:

[Serializable()]
[XmlRoot("root")]
public class SerializeClass
{
   public string Comment { get; set; }
        
   public List<Person> PeopleList { get; set; }

   public SerializeClass()
   {
       Comment = "This is Test List of People";
   }
}

I teraz modyfikujemy klasę PersonRepository - dodajemy pole SerializeClass, która będzie wykorzystywana do zapisu oraz odczytu z pliku. Należy zwrócić uwagę na zmiany metod SaveToXml() i  LoadFromXml() Oto kod zmodyfikowanej klasy:

public class PeopleRepository : IRepository
{
    private string _assemblyDirectory;

    private static int _peopleersonId;

    public List<Person> PeopleList { get; set; }

    private SerializeClass serializeClass;

    private int SetNewId()
    {
        return ++_peopleersonId;
    }

    public void AddPerson(Person person)
    {
        if (PeopleList == null)
        {
            PeopleList = new List<Person>();
        }
        person.PersonId = SetNewId();
        PeopleList.Add(person);
    }
    public PeopleRepository(string assemblyDirectory = @"d:/peoplelist.xml")
    {
        _assemblyDirectory = assemblyDirectory;
    }

    public void SaveToXml()
    {
        serializeClass = new SerializeClass();
        serializeClass.PeopleList = this.PeopleList;

        var xSerializer = new XmlSerializer(typeof(SerializeClass));

        using (var writer = new StreamWriter(_assemblyDirectory))
        {
            xSerializer.Serialize(writer, serializeClass);
        }
    }

    public void LoadFromXml()
    {
        var tmpXmlSerializer = new XmlSerializer(typeof(SerializeClass));

        using (FileStream readFileStream = new FileStream(_assemblyDirectory, FileMode.Open, FileAccess.Read, FileShare.Read))
        {
            object obj = tmpXmlSerializer.Deserialize(readFileStream);

            serializeClass = (SerializeClass)obj;
            PeopleList = serializeClass.PeopleList;
        }
    }
}

W klasie Widoczne są atrybuty:

[Serializable()]
[XmlRoot("root")]

Pierwszy z nich określa, że obiekty tej klasy mogą być serializowane czyli zapisywane do XML'a. Drugi określa nazwę głównej gałęzi. W klasie Person określiłem atrybut:

[XmlAttribute("id")]

Który zamienia z kolei atrybut w XML'u  z elementu

<PersonId>1</PersonId> na  <Person id="1">


Kolejny Atrybut zmienia nazwę elementu:


public class SerializePerson
{ 
    [XmlElement("Number")]
    public int HouseNo { get; set; }
    [XmlElement("Street")] 
    public  string StreetName { get; set; } 
    [XmlElement("CityName")]
}

Efekt jest następujący:

<AddressDetails> 
  <Number>4</Number>
  <Street>Rohini</Street>
  <CityName>Delhi</CityName>
</AddressDetails>

Kolejny [XmlIgnore] powoduje wyrzucenie pola w klasie z procesu serializacji.

public class AddressDetails
{
    [XmlElement("Number")]
    public int HouseNo;
    [XmlElement("Street")]
    public string StreetName;
    [XmlIgnore]
    public string City;
}

Oto wynik kompilacji - pominięto w serializacji pole City:

<AddressDetails>
    <Number>4</Number>
    <Street>ABC</Street>
</AddressDetails>

Materiał został częściowo pobrany z CodeProject.





wtorek, 27 października 2015

XmlSerializer - Cz1. Serializacja, deserializacja ( + Extension Methods)

W tym poście przedstawię proste sposoby na serializację i deserializację danych. Przy okazji przedstawię również pewnie już znane extension methods. Nie będę się tu rozwodził na składnią samej aplikacji więc wykonamy to po prostu w zwykłej Consoli. Wykonajmy więc Listę osób, które do których przypisany będzie zestaw maili i loginów z których korzystają te osoby. Stwórzmy zatem aplikację konsolową:



W tym celu wykonamy prostą klasę Person, która będzie częścią listy People. Każda osoba posiada przypisany zestaw linków w polu List<Email>.

public class Person
{
    public int PersonId { get; set; }

    public string Name { get; set; }

    public string Surname { get; set; }

    public List<Email> Emails { get; set; }
}

Natomiast prosta klasa Email wygląda następująco:

public class Email
{
    public string EmailValue { get; set; }

    public string UserName { get; set; }
}

Teraz Przejdźmy do repozytorium:

interface IRepository
{
    void AddPerson(Person person);

    void SaveToXml();

    void LoadFromXml();
}

I sama klasa:

class PopleRepository : IRepository
{
    private string _assemblyDirectory;

    private static int _peopleersonId;

    public List<Person> PeopleList { get; set; }

    private int SetNewId()
    {
        return ++_peopleersonId;
    }

    public void AddPerson(Person person)
    {
        if (PeopleList == null)
        {
            PeopleList = new List<Person>();
        }
        person.PersonId = SetNewId();
        PeopleList.Add(person);
    }
    //....Kolejne metody IRepository
}

No własnie.. i tu przechodzimy do sedna. Tak będą wyglądały Metody zapisu i odczytu z xml'a:

public void SaveToXml()
{
    var xSerializer = new XmlSerializer(typeof(List<Person>));

    using (var writer = new StreamWriter(_assemblyDirectory))
    {
        xSerializer.Serialize(writer, PeopleList);
    }
}

public void LoadFromXml()
{
    var tmpXmlSerializer = new XmlSerializer(typeof(List<Person>));

    var reader = new StreamReader(_assemblyDirectory);

    object obj = tmpXmlSerializer.Deserialize(reader);

    PeopleList = obj as List<Person>;
}

Przy zapisie musimy stworzyć typ XmlSerializer i określić jakiego typu wartości będą w nim umieszczone. SteramWriter podobnie jak StreamReader tworzy specjalną instancję ścieżki zapisu/odczytu wymaganą przez XmlSerializer. Pole _assemblyDirectory określa ścieżkę do pliku.

W celu nadania losowych wartości polecam odwiedzić stronę Faker.net. Znajdują się tam biblioteki do nadawania losowych wartości teleadresowych. Wystarczy uruchomić Package Manager Console i tam wpisać następującą komendę:

PM> Install-Package Faker.Net

I już możemy dodawać wartości z bibliotek w następujący sposób:

new Person
{
    Name = Faker.Name.First(),
    Surname = Faker.Name.Last(),
};

Biblioteka o niewymownej nazwie sama wygeneruje nam losowo imiona i nazwiska stworzonych osób.

Stwórzmy na szybko jeszcze jakąś metodę w klasie program generującą poszczególne osoby w repozytorium:

/// <summary>
/// Klasa Generująca repozytorium osób
/// </summary>
/// <param name="noOfPeople">Ilośc osób w repozytorium</param>
/// <param name="path">Ścieżka pliku do zapisu</param>
/// <returns></returns>
public static PeopleRepository GenerateRepository(int noOfPeople, string path = @"d:/testPath.text")
{
    var genTmpRepo = new PeopleRepository();

    var rndNoOfEmails = new Random();

    for (int i = 0; i < noOfPeople; i++)
    {
        genTmpRepo.AddPerson(
            new Person
            {
                Name = Faker.Name.First(),
                Surname = Faker.Name.Last(),
            });
    }

    foreach (var per in genTmpRepo.PeopleList)
    {
        int tmpVal = rndNoOfEmails.Next(1, 5);
        for (int i = 0; i < tmpVal; i++)
        {
            per.Emails.Add(new Email
                {
                    EmailValue = Faker.Internet.Email(),
                    Username = Faker.Internet.UserName()
                });
        }
    }
    return genTmpRepo;
}

Przy okazji wykonajmy jeszcze metodę rozszerzającą SimpleRepositoryExt, aby sprawdzić co zostało wygenerowane:

public static class SimpleRepositoryExt
{
    public static void ShowValues(this PeopleRepository peopleRepository)
    {
        foreach (var per in peopleRepository.PeopleList)
        {
            Console.WriteLine("Id : {0} Name : {1}, Surname : {2}", per.PersonId, per.Name, per.Surname);
            Console.WriteLine("----------------------------");
            foreach (var em in per.Emails)
            {
                Console.WriteLine("Login : {0}\n Email : {1}", em.Username, em.EmailValue);
            }
            Console.WriteLine("\n\n\n");
        }
    }
}

Przejdźmy teraz do klasy Program i wykorzystajmy stworzone metody do zapisania wartości do pliku txt. Jak widać encji tmpPeopleRep mamy dostępne własciwości, metody klasy oraz metody rozszerzające:

static void Main(string[] args)
{
    PeopleRepository tmpPeopleRep = GenerateRepository(3);
    if (tmpPeopleRep == null)
    {
        tmpPeopleRep = new PeopleRepository();
    }
    tmpPeopleRep.SaveToXml();
    tmpPeopleRep.ShowValues();
}

Metoda rozszerzająca ShowValues po odpaleniu kompilatora wyświetla następującą treść: 


Zerknijmy do treści zawartej w pliku XML:

<?xml version="1.0" encoding="utf-8"?>
<ArrayOfPeron xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Person>
    <PersonId>1</PersonId>
    <Name>Zelma</Name>
    <Surname>Price</Surname>
    <Emails>
      <Email>
        <EmailValue>stephen@braun.ca</EmailValue>
        <Username>cecil.cruickshank</Username>
      </Email>
    </Emails>
  </Person>
  <Person>
    <PersonId>2</PersonId>
    <Name>Rowena</Name>
    <Surname>Morissette</Surname>
    <Emails>
      <Email>
        <EmailValue>jane_armstrong@medhurstbatz.name</EmailValue>
        <Username>blair</Username>
      </Email>
      <Email>
        <EmailValue>alvina_harber@nienow.biz</EmailValue>
        <Username>liza</Username>
      </Email>
      <Email>
        <EmailValue>quinten@schummokuneva.biz</EmailValue>
        <Username>harley_marks</Username>
      </Email>
      <Email>
        <EmailValue>burley.koelpin@oconner.co.uk</EmailValue>
        <Username>benedict</Username>
      </Email>
    </Emails>
  </Person>
  <Person>
    <PersonId>3</PersonId>
    <Name>Asha</Name>
    <Surname>Sipes</Surname>
    <Emails>
      <Email>
        <EmailValue>margie_ratke@steuber.uk</EmailValue>
        <Username>marge</Username>
      </Email>
      <Email>
        <EmailValue>tanya.runolfsson@baumbach.info</EmailValue>
        <Username>litzy.stamm</Username>
      </Email>
    </Emails>
  </Person>
</ArrayOfPerson>

Jak widać wartości są przekonwertowane do szybkiego odczytu przez aplikacje również użytkownik może względnie przejrzeć zawartość. Odczytajmy teraz zapisane wartości.

static void Main(string[] args)
{
    PeopleRepository tmpPeopleRep = new PeopleRepository();
    tmpPeopleRep.LoadFromXml();
    tmpPeopleRep.ShowValues();
}

Wartości zostaną wczytane do listy i wyświetlone. To tyle w części pierwszej. W części drugiej opiszę trochę więcej o prawidłowej składni klas do obsługi zapisu i odczytu danych z XML


sobota, 24 października 2015

WPF - Podstawy wzorca MVVM Cz 4.- ICommand i obsługa przycisków i bindowanie

Dziś zajmiemy się obsługą przycisków wg wzorca MVVM będziemy bindować również wartości z TextBoxów. 



Jako Pierwszym najłatwiejszym przyciskiem, który obsłużymy jest Button Zamknij aplikację. W tym celu zapoznamy się z klasą RelayCommand:

public class RelayCommand : ICommand
    {
        readonly Action<object> _execute;
        readonly Predicate<object> _canExecute;

        public RelayCommand(Action<object> execute)
            : this(execute, null)
        {
        }

        public RelayCommand(Action<object> execute, Predicate<object> canExecute)
        {
            if (execute == null)
                throw new ArgumentNullException("execute");

            _execute = execute;
            _canExecute = canExecute;
        }

        public bool CanExecute(object parameters)
        {
            return _canExecute == null ? true : _canExecute(parameters);
        }

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

        public void Execute(object parameters)
        {
            _execute(parameters);
        }
    }

Na pierwszy rzut oka może się to wydać skomplikowane, jednak warte uwagi są jedynie dwie metody Execute i CanExecute, a także konstruktor klasy. Jak widać konstruktor wymaga delegatów. Pierwszy argument ma być metodą, która nic nie zwraca i może przyjąć parametr typu object. Jak sama nazwa wskazuje w konstruktorze "_execute" - jest to metoda, która coś wykona w przypadku jak zostanie wykonana czynność czyli np naciśnięcie przycisku. Kolejny delegat to Predicate czyli metoda, która zwraca bool i może przyjąc parametr typu object przy pomocy tej metody będzie sprawdzany warunek czy metoda w delegacie _execute może w ogóle zostać wykonana. Jeśli skorzystamy z drugiego konstruktora czyli:

public RelayCommand(Action<object> execute) : this(execute, null) {}

od razu metoda _canexecute zwracać będzie wartość true czyli warunek zawsze będzie spełniony. Stwórzmy zatem nowy folder o nazwie "helperClasses"  i umieścimy sobie tam w/w klasę.


Jako pierwsze zaimplementujmy metodę wykonawczą, która będzie wyłączała program podczas naciśnięcia przycisku:

private void CloseApplication(object obj)
{
    Application.Current.Shutdown();
}

Następnie zaimplementujmy właściwość, która obsłuży przycisk. Zwróćmy uwagę na to, że xaml ma już wbudowaną obsługę interfejsu ICommand, której strukturę za chwilę przedstawię. a więc z poziomu MainWindowViewModel właściwość będzie wyglądała następująco:

private ICommand _shutdownApplicationCommand = null;

public ICommand ShutApplicationCommand
{
    get
    {
        if (_shutdownApplicationCommand == null)
        {
            _shutdownApplicationCommand = new RelayCommand(
                p => CloseApplication(null), null);
        }
        return _shutdownApplicationCommand;
    }
}

Teraz Przejdźmy do MainWindow i zaimplementujmy obsługę przycisku "Zamknij Aplikację" w xaml:

<Button Name="CloseApp" Content="Zamknij aplikację" Height="40" Margin="4" 
                    Command="{Binding ShutApplicationCommand,Source={StaticResource MainWindowViewM}}"/>

Gotowe. Zauważmy, że możemy zbindować właściwość znajdującą się w klasie MainWindowViewModel i po naciśnięciu uruchamia przypisaną do niej metodę.

Przejdźmy teraz do przycisku "Usuń Pracownika". Zaczniemy od dodania metody Remove w klasie WorkerRepository:


public void Remove(Worker worker)
{
    _studentList.Remove(_studentList.Single(x=>x.WorkerId == worker.WorkerId));
}

Tworzymy teraz kolejną klasę do obsługi nazwijmy ją ObservableObject, która dziedziczy po INotifyPropertyChanged. Wewnątrz klasy znajduje się delegat INotifyPropertyChanged.PropertyChangedEventHandler, który będzie obsługiwał zdarzenie, gdy właściwość zostanie zmieniona:

public class ObservableObject : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    public void OnPropertyChanged(string ev)
    {
       PropertyChange(new PropertyChangedEventArgs(ev));
    }

    private void PropertyChange(PropertyChangedEventArgs ev)
    {
       if (PropertyChanged != null)
       {
           PropertyChanged(this, ev);
       }
     }
}

Przejdźmy teraz do MainWindowViewModel i stwórzmy właściwość, gdzie będzie przechowywana wartośc wybranego wiersza, dodajmy także pole ICommand, które obsłuży nasz przycisk a także 2 metody. Jedna z nich będzie sprawdzała, czy został zaznaczony któryś z wierzy do usunięcia (jeśli nie zostanie wybrany żaden z wierszy przycisk będzie nieaktywny). W drugiej metodzie będzie implementacja usunięcia wybranej wartości z listy:

public class MainWidnowViewModel : ObservableObject
    {
        //...

        public WorkerRowViewModel SelectedWorkerRow { get; set; }

        private ICommand _eraseRow = null;

        public ICommand EraseRowCommand
        {
            get
            {
                if (_eraseRow == null)
                {
                    _eraseRow = new RelayCommand(p => EraseSelectedRow(SelectedWorkerRow),
                        p=>CheckIsSelectedRow(SelectedWorkerRow));
                }
                return _eraseRow;
            }
        }

        private void EraseSelectedRow(WorkerRowViewModel value)
        {
            _repository.Remove(new Worker {WorkerId = value.WorkerId});
            OnPropertyChanged("WorkerList");
        }

        private bool CheckIsSelectedRow(WorkerRowViewModel workerRowViewModel)
        {
            if (workerRowViewModel == null)
            {
                return false;
            }
            return true;
        }
       //....
    }
}

Metoda w powyższym kodzie OnPropertyChanged("WorkerList"); Umożliwia odświeżanie DataGrid, gdy zostanie usunięty którykolwiek z rekordów. Teraz Przejdźmy do widoku okna i zaimplementujemy przekazanie wartości z rekordu DataGrid'a do wspomnianej właściwości SelectedWorkerRow w MainWindowViewModel:

<DataGrid Name="DGrStudentsTable" Grid.Column="1" Margin="5" Grid.ColumnSpan="2" 
           CanUserAddRows="False" AutoGenerateColumns="False"
           ItemsSource="{Binding Source={StaticResource MainWindowViewM}, Path=WorkerList}"
           SelectedItem="{Binding Source={StaticResource MainWindowViewM},Path=SelectedWorkerRow, Mode=TwoWay}"
          >
    <DataGrid.Columns>
        <DataGridTextColumn Header="Id" Binding="{Binding WorkerId}"/>
        <DataGridTextColumn Header="Temat" Binding="{Binding Name}"/>
        <DataGridTextColumn Header="Nazwisko" Binding="{Binding Surname}"/>
        <DataGridTextColumn Header="Satus Pracownika" Binding="{Binding WorkerStatus}"/>
    </DataGrid.Columns>
</DataGrid>
Teraz można usuwać rekordy, jednak, gdy nie wybierzemy żadnej z wierszy - przycisk nie będzie aktywny:


Przejdźmy do ostatniej sekcji tego wpisu - Przycisku - "Dodaj Pracownika". 

Aby wypełnić wartościami - najlepiej byłoby przenieść nasz słownik, który do tej pory był w właściwości WorkerList do pola w Klasie.

public class MainWidnowViewModel : ObservableObject
    {
        private Dictionary<eStatus, string> eStatusDictionary = new Dictionary<eStatus, string>
        {
            {eStatus.CodeWriter,"Programista"},
            {eStatus.TeamManager,"Zwierzchnik Drużyny"},
            {eStatus.Tester, "Tester Programów"}
        };
//...... Reszta klasy
}

Dzięki temu obsłużymy oczywiście wcześniejszą właściwość WorkerList i dodatkową właściwość dla Combobox'a:

public WorkerRowViewModel SelectedWorkerRow { get; set; }

        public List<string> WorkerStatusToString
        {
            get
            {
                return eStatusDictionary.Values.ToList();
            }
        }
}

Teraz przechodzimy do Combobox'a w MainWindow.xaml. I uzupełniamy o atrybut:

<ComboBox Name="CboxStatus" Style="{StaticResource CustomXBoxStyle}"
                      ItemsSource="{Binding Source={StaticResource MainWindowViewM},Path=WorkerStatusToString}"
                      IsSynchronizedWithCurrentItem="True"
/>

Kolejnym Etapem będzie obsługa ostatniego przycisku "Dodaj Pracownika"  Przejdźmy więc do klasy WorkerRepository i stwórzmy nową metodę dodającą nowego pracownika. Kolejne Id pracownika musi być przydzielane do każdego nowo dodanego pracownika dołóżmy zatem jeszcze prywatną metodę generującą Id:


private static int ActualId;

private int GenereateId()
{
    return ++ActualId;
}

I metoda dodająca pracownika do "Bazy Danych":

public void Add(Worker worker)
{
    worker.WorkerId = GenereateId();
    _studentList.Add(worker);
}

W klasie MainWindowViewModel, gdzie dodajemy Właściwości do obsługi TextBoxów: Imię, Nazwisko i Comboboxa - Status:

private string _nameValueText;

public string NameValueText
{
    get
    {
        return _nameValueText;
    }
    set
    {
        _nameValueText = value;
        OnPropertyChanged("NameValueText");
    }
}

private string _surnameValueText;

public string SurnameValueText
{
    get
    {
        return _surnameValueText;
    }
    set
    {
        _surnameValueText = value;
        OnPropertyChanged("SurnameValueText");
    }
}

private string _statusValueText;

public string StatusValueText
{
    get
    {
        return _statusValueText;
    }
    set
    {
        _statusValueText = value;
        OnPropertyChanged("StatusValueText");
    }
}

Teraz w MainWindow.xaml Wprowadzamy Implementację odczytu tych właściwości z poziomu kontrolek. Warto zwrócić uwagę na to, że argument Mode ustawiamy na "TwoWay" co oznacza, że dane z kontroli będą odsyłane od strony użytkownika poprzez wpis do TextBoxa jak i również z poziomu źródła (zmazanie wartości po wprowadzeniu danych).

<TextBox Name="TxtName" Height="40" Width="120" Text="{Binding Source={StaticResource MainWindowViewM},
                Path=NameValueText,Mode=TwoWay}" Style="{StaticResource CustomTextBoxStyle}"/>
<TextBox Name="TxtSurname" Height="40" Width="120" Style="{StaticResource CustomTextBoxStyle}"
                Text="{Binding Source={StaticResource ResourceKey=MainWindowViewM},Path=SurnameValueText,Mode=TwoWay}"/>
<ComboBox Name="CboxStatus" Style="{StaticResource CustomXBoxStyle}"
                      ItemsSource="{Binding Source={StaticResource MainWindowViewM},
                Path=WorkerStatusToString}" IsSynchronizedWithCurrentItem="True"
                      SelectedValue="{Binding Source={StaticResource MainWindowViewM},
                Path=StatusValueText}"
/>

W Comboboxie można wyróżnić dwa atrybuty: Itemsource oraz SelectedValue. Itemsource określa skąd mają być pobierane dane wyświetlane w comboboxie. Wartości te muszą być podane w formie listy i również muszą być przekonwertowane na typ string. Stwórzmy teraz obsługę przycisku. Jak w przypadku innych buttonów musimy zastosować właściwość typu ICommand i przypisać do typu RelayCommand dwie metody - jedna sprawdzająca czy zostały wypełnione wskazane TextBoxy druga wykonująca procedurę wprowadzania wartości z kontrolek do listy _repository.

private ICommand _eraseRow = null;

public ICommand EraseRowCommand
{
    get
    {
        if (_eraseRow == null)
        {
            _eraseRow = new RelayCommand(p => EraseSelectedRow(SelectedWorkerRow),
                p => CheckIsSelectedRow(SelectedWorkerRow));
        }
        return _eraseRow;
    }
}

private void EraseSelectedRow(WorkerRowViewModel value)
{
    _repository.Remove(new Worker { WorkerId = value.WorkerId });
    OnPropertyChanged("WorkerList");
}

private bool CheckIsSelectedRow(WorkerRowViewModel workerRowViewModel)
{
    if (workerRowViewModel == null)
    {
        return false;
    }
    return true;
}

Zadanie wykonane. Wszytkie nasze kontrolki działają. Wpisanie nowego pracownika wymaga wypełnienia Textboxów oraz wskazanie statusu w comboboxie. Usunięcie pracownika wymaga wybranie go z listy. Zgodnie z wzorcem MVVM nie powinniśmy wprowadzać implementacji w tzw. CodeBehind MainWindow.xaml.cs i rzeczywiście pliku nie zostały wprowadzane żadne zmiany.

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }
}

Oto nasz gotowy projekt:


Pod linkiem znajduje się cały projekt z 3-częściowego kursu: