W Prism mamy do dyspozycji dwa kluczowe sposoby poruszania się po aplikacji: State-Based Navigation oraz View Based Navigation. W tym artykule omówię pierwszy z tych sposobów.
Base-State Navigation odnosi się do zmiany stanu kontrolek na formatce czyli np. ukrycie kontrolki, pokazanie, dodawanie animacji itp. Nawigacja ta występuje w obrębie pojedynczego widoku.
Obsługa tego typu nawigacji może być spowodowana różnymi rodzaju zachowaniami np.:
- zmiana wartości właściwości w ViewModelu
- interakcja użytkownika z aplikacją (kliknięcie na kontrolkę itp.)
- zmiana sposobu prezentowania danych użytkownika
Kiedy nie używać Base-State Navigation:
- kiedy chcemy wyświetlać dane z różnych źródeł
- użytkownik chce wykonywać zadania nie związane z tym co aktualnie widzi na ekranie
- skomplikowane zmiany wyglądu
Po tym krótkim wstępie czas na jakieś przykładowe aplikacje, które pokażą w praktyce o co chodzi w opisanej powyżej nawigacji.
1. Powiadamianie o stanie aplikacji.
W tym przykładzie pokażemy użytkownikowi co dzieje się podczas działania naszej aplikacji.
Na początek stwórzmy nasz projekt który będzie mieć następującą postać:
Mając ten szablon przyda się nam kod typów tutaj występujących:
Customer.cs:
Code:
namespace BusinessObjects
{
public class Customer
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string CompanyName { get; set; }
public string EmailAddress { get; set; }
public string Phone { get; set; }
}
}
IRepository.cs:
Code:
namespace Repositories
{
public interface IRepository
{
}
}
ICustomerRepository.cs:
Code:
using System.Collections.Generic;
using BusinessObjects;
namespace Repositories.Customers
{
public interface ICustomerRepository : IRepository
{
List<Customer> GetAllCustomers();
}
}
CustomerRepository.cs:
Code:
using System.Collections.Generic;
using System.Data.SqlClient;
using BusinessObjects;
using Repositories.Customers;
namespace MSSQLRepository.Customers
{
public class CustomerRepository : ICustomerRepository
{
public List<Customer> GetAllCustomers()
{
var customerList = new List<Customer>();
using (var conn = new SqlConnection(Configuration.ConnectionString))
{
using(var command = conn.CreateCommand())
{
command.CommandText = "SELECT CustomerID, FirstName, LastName, CompanyName, EmailAddress, Phone " +
"FROM SalesLT.Customer";
conn.Open();
using(var dr = command.ExecuteReader())
{
while(dr.Read())
{
customerList.Add(CustomerMapper(dr));
}
dr.Close();
}
}
}
return customerList;
}
private static Customer CustomerMapper(SqlDataReader dr)
{
var customer = new Customer
{Id = int.Parse(dr["CustomerID"].ToString()), FirstName = (string) dr["FirstName"]};
customer.FirstName = (string)dr["LastName"];
customer.CompanyName = (string)dr["CompanyName"];
customer.EmailAddress = (string)dr["EmailAddress"];
customer.Phone = (string)dr["Phone"];
return customer;
}
}
}
Configuration.cs:
Code:
namespace MSSQLRepository
{
public class Configuration
{
public static string ConnectionString =
"Data Source=localhost;Initial Catalog=AdventureWorksLT2008R2;Integrated Security=SSPI;";
}
}
BaseViewModel.cs:
Code:
using System.ComponentModel;
namespace Infrastructure
{
public class BaseViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
RegionNames.cs:
Code:
namespace Infrastructure
{
public static class RegionNames
{
public static string MainContentRegion = "MainContentRegion";
}
}
Bootstrapper.cs:
Code:
using System.Windows;
using MSSQLRepository.Customers;
using Microsoft.Practices.Prism.Modularity;
using Microsoft.Practices.Prism.UnityExtensions;
using Microsoft.Practices.Unity;
using Repositories.Customers;
namespace Sample1
{
public class Bootstrapper : UnityBootstrapper
{
protected override void ConfigureContainer()
{
base.ConfigureContainer();
Container.RegisterType(typeof (ICustomerRepository), typeof (CustomerRepository), new ContainerControlledLifetimeManager());
}
protected override IModuleCatalog CreateModuleCatalog()
{
var moduleCatalog = base.CreateModuleCatalog();
var customerModule = typeof (CustomerModuleCustomerModule);
moduleCatalog.AddModule(new ModuleInfo(customerModule.Name, customerModule.AssemblyQualifiedName));
return moduleCatalog;
}
protected override void InitializeShell()
{
Application.Current.MainWindow = (Shell)Shell;
Application.Current.MainWindow.Show();
}
protected override DependencyObject CreateShell()
{
return Container.Resolve<Shell>();
}
}
}
Shell.xaml.cs:
Code:
<Window x:Class="Sample1.Shell"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:prism="clr-namespace:Microsoft.Practices.Prism.Regions;assembly=Microsoft.Practices.Prism"
xmlns:Infrastructure="clr-namespace:Infrastructure;assembly=Infrastructure" Title="Shell" Height="300" Width="300">
<Grid>
<ContentControl prism:RegionManager.RegionName="{x:Static Infrastructure:RegionNames.MainContentRegion}" />
</Grid>
</Window>
Czas na nasz moduł - CustomerModule:
Code:
<UserControl x:Class="CustomerModule.CustomerListView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d" >
<Grid>
<DataGrid ItemsSource="{Binding CustomerList, Mode=TwoWay}" />
</Grid>
</UserControl>
CustomerListViewModel.cs:
Code:
using System.Collections.Generic;
using System.ComponentModel;
using System.Threading;
using System.Threading.Tasks;
using BusinessObjects;
using Infrastructure;
using Repositories.Customers;
namespace CustomerModule
{
public class CustomerListViewModel : BaseViewModel
{
private readonly ICustomerRepository _customerRepository;
private List<Customer> _customerList;
public List<Customer> CustomerList
{
get
{
if(_customerList != null)
{
return _customerList;
}
var loadCustomerTask = new Task(() =>
{
Thread.Sleep(5000);
CustomerList = _customerRepository.GetAllCustomers();
});
loadCustomerTask.Start();
return _customerList;
}
set
{
_customerList = value;
RaisePropertyChanged("CustomerList");
}
}
public CustomerListViewModel(ICustomerRepository customerRepository)
{
_customerRepository = customerRepository;
}
}
}
Code:
using Infrastructure;
using Microsoft.Practices.Prism.Modularity;
using Microsoft.Practices.Prism.Regions;
namespace CustomerModule
{
public class CustomerModule : IModule
{
private readonly IRegionManager _regionManager;
public CustomerModule(IRegionManager regionManager)
{
_regionManager = regionManager;
}
public void Initialize()
{
_regionManager.RegisterViewWithRegion(RegionNames.MainContentRegion, typeof (CustomerListView));
}
}
}
Dla przypomnienia sposób powyższy to View Discovery - czyli widok zostanie załadowany zaraz po uruchomieniu naszej aplikacji.
Opiszmy naszą aplikację - mamy do czynienia z bazą (AdventureWorks) z której są pobierane dane i ładowane do kontrolki Grid. Symulujemy tutaj 5 sekundowe opóźnienie podczas ładowania danych. Co w tym momencie widzi użytkownik? Właściwie nic. Będzie musiał poczekać 5 sekund. Nawet jeśli poczeka (nie wyłączy wcześniej aplikacji) to jednak i tak nie wie do końca co dzieje się w tym momencie.
Należy więc poinformować użytkownika co dzieje się w danym momencie z naszą aplikacją.
W tym celu skorzystamy z kontrolki BusyIndicator dostępnej w pakiecie Extended WPF Toolkit.
Do naszego kodu XAML dokładamy następujące linijki:
Code:
<UserControl x:Class="CustomerModule.CustomerListView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:extToolkit="http://schemas.microsoft.com/winfx/2006/xaml/presentation/toolkit/extended"
mc:Ignorable="d" >
<Grid>
<extToolkit:BusyIndicator IsBusy="{Binding IsBusy}" BusyContent="Loading data...">
<DataGrid ItemsSource="{Binding CustomerList, Mode=TwoWay}" />
</extToolkit:BusyIndicator>
</Grid>
</UserControl>
Do ViewModelu:
Code:
using System.Collections.Generic;
using System.ComponentModel;
using System.Threading;
using System.Threading.Tasks;
using BusinessObjects;
using Infrastructure;
using Repositories.Customers;
namespace CustomerModule
{
public class CustomerListViewModel : BaseViewModel
{
private readonly ICustomerRepository _customerRepository;
private List<Customer> _customerList;
public List<Customer> CustomerList
{
get
{
IsBusy = true;
if(_customerList != null)
{
IsBusy = false;
return _customerList;
}
var loadCustomerTask = new Task(() =>
{
Thread.Sleep(5000);
CustomerList = _customerRepository.GetAllCustomers();
IsBusy = false;
});
loadCustomerTask.Start();
return _customerList;
}
set
{
_customerList = value;
RaisePropertyChanged("CustomerList");
}
}
private bool _isBusy;
public bool IsBusy
{
get { return _isBusy; }
set { _isBusy = value;
RaisePropertyChanged("IsBusy");
}
}
public CustomerListViewModel(ICustomerRepository customerRepository)
{
_customerRepository = customerRepository;
}
}
}
Użytkownik po uruchomieniu programu otrzyma następujący rezultat:
Jest to znacznie przyjemniejszy widok, niż czekanie przed pustym ekranem - bez wiedzy czy aplikacja jeszcze działa i odpowiada na nasze komendy.
W następnym poście pokaże w jaki sposób za pomocą stylów uzyskać różne widoki naszej aplikacji Prism.
Brak komentarzy:
Prześlij komentarz