MVVM #5–Modul A

by Lukáš Kubis března 04, 2011 15:59

Předchozí díly:

  1. MVVM #1–Úvod
  2. MVVM #2–Příprava
  3. MVVM #3–Shell
  4. MVVM #4–Shell–Design Data

V dnešním díle si ukážeme jak vytvořit nový modul a jak ho provázat s hlavní aplikací.

Náš první modul bude obsahovat jednoduchou funkcionalitu pro práci s časem. Vytvoříme si tedy nový projekt typu Class Library ve kterém si vytvoříme podobnou strukturu jak můžete vidět na obrázku níže.

image

Dále nesmíme zapomenout přidat referenci na knihovnu MVVMSamle.Shared. Momentálně bude náš viewmodel velice jednoduchý. Bude obsahovat pouze jednu vlastnost DateTime, která zobrazí aktuální datum a čas.

public class AViewModel : WorkspaceViewModel
{
    private string _dateTime;
    public string DateTime
    {
        get { return _dateTime; }
        set
        {
            _dateTime = System.DateTime.Now.ToString();
            OnPropertyChanged(MethodBase.GetCurrentMethod());
        }
    }

    public AViewModel()
    {
        DisplayName = Shared.Strings.ModuleA_Action1_DisplayName;
        Initialize();
    }

    protected override void OnInitialize()
    {
        DateTime = System.DateTime.Now.ToString();
    }
}

V našem View bude pouze textblock pro zobrazení této vlastnosti

<UserControl x:Class="MVVMSample.Modules.Views.AView"
             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" 
             d:DesignHeight="300" d:DesignWidth="300">
    <Grid>
        <TextBlock FontSize="48" Text="{Binding DateTime}"/>            
    </Grid>
</UserControl>

Pokud máme vytvořený modul, je potřeba o něm nějak říct naší hlavní Shell aplikaci. V Shell projektu upravíme třídu App. V metodě OnStartup nastavíme vlastnost GroupCommands třídy MainWindowViewModel. Zatím zde bude pouze jeden GroupCommand (Modul A). A tento GroupCommand bude mít pouze jeden Command (ViewModelA1), který po spuštění zobrazí (vytvoří novou záložku) A1View.

public partial class App : Application
{
    protected override void OnStartup(StartupEventArgs e)
    {
        MainWindowViewModel viewModel = new MainWindowViewModel();
        viewModel.GroupCommands = new List<GroupCommand>()
            {
                new GroupCommand()
                    {
                        GroupName = Shared.Strings.GroupCommand_ModuleA,
                        Commands = new List< RelayCommand >()
                        {
                            new UICommand(param => viewModel.AddWorkspace(new Modules.ViewModels.A1ViewModel()))
                            {
                                Name = Shared.Strings.Command_ViewModelA1
                            }
                        }
                    }
            };

        Shell.MainWindow mainWindow = new MainWindow();
        mainWindow.DataContext = viewModel;
        mainWindow.Show();
    

Pokud uživatel klikne na tlačítko ViewModelA1 tak se do kolekce workspaces přidá nový viewmodel A1ViewModel. Pokud by jsme aplikaci spustili, tak by se místo A1View zobrazil pouze textový popis daného viewmodelu. Proto je zapotřebí přidat do projektu nový ResourceDictionary

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:aVm="clr-namespace:MVVMSample.Modules.ViewModels;assembly=MVVMSample.Modules.ModuleA" 
                    xmlns:aVw="clr-namespace:MVVMSample.Modules.Views;assembly=MVVMSample.Modules.ModuleA">
    
    <DataTemplate DataType="{x:Type aVm:AViewModel}">
        <aVw:AView />    
    </DataTemplate>
    
</ResourceDictionary>

Ve kterém nastavíme pro daný viewmodel příslušné view. Všechno toto nastavení provedeme pomocí DataTemplate. Když spustíme aplikace a zobrazíme si A1View, budeme mít k dispozici i tlačítko pro Refresh, ktreré zavolá vždy metodu Initialize a provede aktualizaci času

Jednoduchý modul máme za sebou a příště si ukážeme jak např. komunikovat mezi moduly.

Tags:

MVVM #4–Shell–Design Data

by Lukáš Kubis března 03, 2011 12:33

Předchozí díly:

  1. MVVM #1–Úvod
  2. MVVM #2–Příprava
  3. MVVM #3–Shell

V minulých dílech jsme si připravili téměř vše podstatné až na hlavní View. To bude předmětem dnešního dílu, kde si kromě vytvoření samotného View ukážeme, jak využít “Design Data” v době návrhu. Aby jsme si tedy mohli otestovat, že náš layout bude odpovídat skutečně tomu co jsme chtěli.

<Window x:Class="MVVMSample.Shell.MainWindow"
		xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
		xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
		Title="MainWindow" Height="768" Width="1024">
	<Grid Background="#ffeeeeee">
		<Grid.Resources>
			<Style x:Key="GroupCommandsTemplate" TargetType="{x:Type ItemsControl}">
				<Setter Property="ItemTemplate">
					<Setter.Value>
						<DataTemplate>
							<Button Content="{Binding Name}"
									Command="{Binding}"
									BorderThickness="0" 
									FontWeight="Normal"
									HorizontalContentAlignment="Left"/>
						</DataTemplate>
					</Setter.Value>
				</Setter>
			</Style>
			<DataTemplate x:Key="ClosableTabItemTemplate">
				<DockPanel Width="120">
					<Button Command="{Binding CloseCommand}"
							Content="X"
							DockPanel.Dock="Right"
							Width="16" Height="16" />
					<ContentPresenter Content="{Binding DisplayName}" />
				</DockPanel>
			</DataTemplate>
		</Grid.Resources>
		<Grid.ColumnDefinitions>
			<ColumnDefinition Width="170"/>
			<ColumnDefinition Width="100"/>
			<ColumnDefinition Width="*"/>
		</Grid.ColumnDefinitions>

		<!-- Navigacni menu - GroupCommands -->
		<ScrollViewer Grid.Row="0" 
					  Grid.RowSpan="2"
					  Grid.Column="0"
					  HorizontalScrollBarVisibility="Hidden"
					  VerticalScrollBarVisibility="Auto">
			<ItemsControl ItemsSource="{Binding GroupCommands}">
				<ItemsControl.ItemTemplate>
					<DataTemplate>
						<Expander Header="{Binding GroupName}"
								  IsExpanded="True"
								  Foreground="#FF97D045"
								  Margin="5,0,0,0"
								  FontWeight="Bold">
							<ItemsControl ItemsSource="{Binding Commands}"
										  Style="{StaticResource GroupCommandsTemplate}"
										  HorizontalContentAlignment="Stretch" 
										  Margin="22,0,10,12" 
										  BorderThickness="0.0"/>
						</Expander>
					</DataTemplate>
				</ItemsControl.ItemTemplate>
			</ItemsControl>
		</ScrollViewer>

		<!-- Pracovni prostor -->
		<Grid Grid.Row="0"
				  Grid.Column="1"
				  Grid.ColumnSpan="2">
			<Grid.ColumnDefinitions>
				<ColumnDefinition Width="100"/>
				<ColumnDefinition Width="*"/>
			</Grid.ColumnDefinitions>
			
			<!-- Ovladaci tlacitka obsahu - Action Commands -->
			<ScrollViewer HorizontalScrollBarVisibility="Hidden"
						  VerticalScrollBarVisibility="Auto">
				<ItemsControl ItemsSource="{Binding SelectedWorkspaceCommands}"
							  Background="#ff888888" 
							  BorderBrush="Black" 
							  BorderThickness="1,0,1,0">
					<ItemsControl.ItemTemplate>
						<DataTemplate>
							<Button Command="{Binding}"
									HorizontalContentAlignment="Center" 
									VerticalContentAlignment="Center"
									Width="80" 
									Height="40"
									Margin="0,5,0,0"
									Content="{Binding Name}">
							</Button>
						</DataTemplate>
					</ItemsControl.ItemTemplate>
				</ItemsControl>
			</ScrollViewer>
			
			<!-- Finalni obsah - SelectedWorkspace -->
			<TabControl ItemsSource="{Binding Workspaces}" 
						Grid.Column="1"
						SelectedItem="{Binding SelectedWorkspace, Mode=TwoWay}"
						ItemTemplate="{StaticResource ClosableTabItemTemplate}"/>
		</Grid>
	</Grid>
</Window>

XAML vypadá ve výsledku takhle:

image

Hmm to nám toho moc neřekne, tak pojďmě si vytvořit nějaký model, který budeme moct použit už při návrhu a uvidíme, zda jsme všechny styly a bindingy nastavili správně.

Pro tuto demonstraci si vytvoříme třídu DesignMainWindowViewModel, který bude dědit ze třídy MainWindowViewModel a DesignWorkspaceViewModel, která bude dědit ze třídy WorkspaceViewModel. Jediné co DesignMainWindowViewModel udělá je, že v konstruktoru naplní potřebné vlastnosti GroupCommands, Workspaces a SelectedWorkspace.

DesignWorkspaceViewModel

namespace MVVMSample.ViewModels
{
    internal class DesignWorkspaceViewModel : WorkspaceViewModel
    {
        public DesignWorkspaceViewModel()
        {
            
        }

        public DesignWorkspaceViewModel(string displayName)
        {
            DisplayName = displayName;
        }
    }
}

DesignWorkspaceViewModel

using System.Collections.Generic;
using MVVMSample.Commands;
using System.Windows;

namespace MVVMSample.ViewModels
{
    internal class DesignMainWindowViewModel : MainWindowViewModel
    {
        public DesignMainWindowViewModel()
        {
            GroupCommand moduleA =  new GroupCommand()
                {
                    GroupName = Shared.Strings.GroupCommand_ModuleA,
                    Commands = new List< RelayCommand >()
                        {
                            new UICommand(
                                execute =>
                                MessageBox.Show(Shared.Strings.Command_ViewModelA1))
                                {
                                    Name = Shared.Strings.Command_ViewModelA1
                                },
                            new UICommand(
                                execute =>
                                MessageBox.Show(Shared.Strings.Command_ViewModelA2))
                                {
                                    Name = Shared.Strings.Command_ViewModelA2
                                },
                            new UICommand(
                                execute =>
                                MessageBox.Show(Shared.Strings.Command_ViewModelA3))
                                {
                                    Name = Shared.Strings.Command_ViewModelA3
                                }
                        }
                };
            GroupCommand moduleB =  new GroupCommand()
                    {
                        GroupName = Shared.Strings.GroupCommand_ModuleB,
                        Commands = new List< RelayCommand >()
                            {
                                new UICommand(
                                    execute =>
                                    MessageBox.Show(Shared.Strings.Command_ViewModelB1))
                                    {
                                        Name = Shared.Strings.Command_ViewModelB1
                                    }
                            }
                    };
            GroupCommand moduleC = new GroupCommand()
                {
                    GroupName = Shared.Strings.GroupCommand_ModuleC,
                    Commands = new List< RelayCommand >()
                        {
                            new UICommand(
                                execute =>
                                MessageBox.Show(Shared.Strings.Command_ViewModelC1))
                                {
                                    Name = Shared.Strings.Command_ViewModelC1
                                }
                        }
                };
            GroupCommands = new List< GroupCommand >()
                {
                    moduleA,moduleB,moduleC
                };

            Workspaces.Add(new DesignWorkspaceViewModel("ViewModelA1"));
            Workspaces.Add(new DesignWorkspaceViewModel("ViewModelA2"));

            SelectedWorkspace = Workspaces[0];
        }
    }
}

Pokud tohle uděláme, můžeme dostat už v době návrhu docela dobrý feedback o našem návrhu.

image

Kromě vytvoření zmiňovaných tříd je zapotřebí v definici XAMLu nastavit, že se jako DataContext má nastavit právě náš DesignMainWindowViewModel

d:DataContext="{d:DesignInstance vm:DesignMainWindowViewModel, IsDesignTimeCreatable=True}"

Příště si už vytvoříme nějaký modul a ukážeme jak nastavit aby se pro ViewModel A zobrazilo View A apod.

Tags:

MVVM #3–Shell

by Lukáš Kubis února 21, 2011 07:06

Předchozí díly:

  1. MVVM #1–Úvod
  2. MVVM #2–Příprava

Jestli vzpomínáte na předchozí díl, byl tam návrh naší aplikace, která se skládala z navigačního menu (GroupCommands), pracovního prostoru (Workspace) a nějakých “akčních” tlačítek.

Co by tedy naše navigačního menu (GroupCommands) mělo obsahovat? Měl by stačit pouze nějaký název a kolekce Commandů, kde každý Command vytvoří novou záložku. Prostě když si kliknu v navigačním menu na nějakou možnost, vytvoří se mi nová záložka a v pracovním prostoru se zobrazí příslušné data.

/// <summary>
/// Trida reprezentujici leve menu
/// </summary>
public sealed class GroupCommand
{
    /// <summary>
    /// Nazev skupiny
    /// </summary>
    public string GroupName { get; set; }

    /// <summary>
    /// Prikazy ktere se provedou po stisku navigacnich tlacitek
    /// </summary>
    public List<RelayCommand> Commands { get; set; }
}

GroupCommand jsme si definovali, ale je třeba vytvořit hlavní ViewModel, který bude mít na starost:

  • zobrazení navigačního menu
  • zobrazení dat v pracovním prostoru
  • vytváření nový záložek

Vytvořme si tedy MainWindowViewModel:

image

Jak si můžete všimnout na obrázku výše, budeme potřebovat pouze kolekci GroupCommands (navigační menu), Workspaces (jednotlivé záložky), SelectedWorkspace (aktuální záložka) a SelectedWorkspaceCommands (“akční" tlačítka). Dále jsou zde ještě metody pro vytvoření či odebrání záložky a pro vytvoření obsluhy žádosti o uzavření záložky.

public class MainWindowViewModel : ViewModelBase
{
    #region | Fields

    private ObservableCollection<WorkspaceViewModel> _workspaces;
    private WorkspaceViewModel _selectedWorkspace;

    #endregion // Fields

    #region | Commands

    /// <summary>
    /// Leve menu
    /// </summary>
    public List<GroupCommand> GroupCommands { get; set; }

    public List<RelayCommand> SelectedWorkspaceCommands
    {
        get
        {
            // pokud neni oznacen workspace vrat prazdnou kolekci
            if ( SelectedWorkspace == null )
                return new List<RelayCommand>();

            return SelectedWorkspace.Commands;
        }
    }

    #endregion // Commands

    #region | Workspaces

    /// <summary>
    /// Aktualni seznam Workspace-u
    /// </summary>
    public ObservableCollection<WorkspaceViewModel> Workspaces
    {
        get
        {
            if ( _workspaces == null )
            {
                _workspaces = new ObservableCollection<WorkspaceViewModel>();
                _workspaces.CollectionChanged += this.OnWorkspacesChanged;
            }
            return _workspaces;
        }
    }

    /// <summary>
    /// Aktualne vybrany Workspace
    /// </summary>
    public WorkspaceViewModel SelectedWorkspace
    {
        get { return _selectedWorkspace; }
        set
        {
            if ( _selectedWorkspace == value )
                return;

            _selectedWorkspace = value;

            // posli notifikaci o zmene vlastnosti SelectedWorkspace
            OnPropertyChanged(MethodBase.GetCurrentMethod());
            OnPropertyChanged("SelectedWorkspaceCommands");
        }
    }

    #endregion // Workspaces

    #region | OnWorkspacesChanged

    /// <summary>
    /// Obsluha udalosti ObservableCollection(WorkspaceViewModelEx).CollectionChanged
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    void OnWorkspacesChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        // pri zmene workspace vytvorim obsluhu udalosti RequestClose

        if ( e.NewItems != null && e.NewItems.Count != 0 )
            foreach ( WorkspaceViewModel workspace in e.NewItems )
                workspace.RequestClose += this.OnWorkspaceRequestClose;

        if ( e.OldItems != null && e.OldItems.Count != 0 )
            foreach ( WorkspaceViewModel workspace in e.OldItems )
                workspace.RequestClose -= this.OnWorkspaceRequestClose;
    }

    #endregion // OnWorkspacesChanged

    #region | OnWorkspaceRequestClose

    /// <summary>
    /// Obsluha udalosti WorkspaceViewModel.RequestClose
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    void OnWorkspaceRequestClose(object sender, EventArgs e)
    {
        // odeberu uzavreny workspace
        Workspaces.Remove(sender as WorkspaceViewModel);
    }

    #endregion // OnWorkspaceRequestClose

    #region | AddWorkspace

    /// <summary>
    /// Prida workspace do seznamu workspace-u
    /// </summary>
    /// <param name="workspaceViewModel"></param>
    public void AddWorkspace(WorkspaceViewModel workspaceViewModel)
    {
        Workspaces.Add(workspaceViewModel);
        SelectedWorkspace = workspaceViewModel;
    }

    #endregion // AddWorkspace
}

Tak už máme MainWindowViewModel, ale nemáme k němu příslušné View. To ovšem napravíme příště.

Tags:

MVVM #2–Příprava

by Lukáš Kubis února 13, 2011 07:17

Předchozí díly:

  1. MVVM #1 – Úvod

V minulém díle jsme si ukázali obrázek architektury a dnes se podíváme na základní layout naší aplikace

image

Úplně v levé části bude navigační menu. Navigační menu bude sloužit pro spouštění akcí jednotlivých modulů. Vedle navigační menu, je prostor pro tzv. “akční” tlačítka. Bude se jednat o tlačítka, která budou provádět nějakou akci pro zrovna zobrazené data. A na opačné pravé straně bude prostor pro zobrazení potřebných dat. Tento prostor bude dále členěn na jednotlivé záložky tak jak to je např. v Internet Exploreru.

ViewModels

Nejdříve si připravíme základní ViewModel-y:

image

ViewModelBase bude obsahovat pouze to nejdůležitější:

  • DisplayName – Název ViewModelu, který se zobrazí na záložce.
  • Tag – vlastnost pro doplňující informace
  • IPropertyChanged – Implementace tohoto rozhraní nám umožní při použití Bindingu notifikovat o změně hodnoty příslušné vlastnosti.

Kód vypadá následně:

public abstract class ViewModelBase : INotifyPropertyChanged, IDisposable
{
    #region | Properties

    #region | DisplayName
    private string _displayName;
    public string DisplayName
    {
        get { return _displayName; }
        protected set
        {
            _displayName = value;
            OnPropertyChanged(MethodBase.GetCurrentMethod());
        }
    }

    #endregion // DisplayName

    #region | Tag
    private string _tag;
    public string Tag
    {
        get { return _tag; }
        protected set
        {
            _tag = value;
            OnPropertyChanged(MethodBase.GetCurrentMethod());
        }
    }
    #endregion // Tag

    #region | Ctor

    protected ViewModelBase()
    {
    }

    #endregion // Ctor

    #endregion // Properties

    #region | Implementation of INotifyPropertyChanged

    public event PropertyChangedEventHandler PropertyChanged;

    /// <summary>
    /// Notifikuje UI o zmene vlastnosti
    /// </summary>
    /// <param name="methodBase"></param>
    protected virtual void OnPropertyChanged(MethodBase methodBase)
    {
        this.OnPropertyChanged(methodBase.Name.Substring(4));
    }

    /// <summary>
    /// Notifikuje UI o zmene vlastnosti
    /// </summary>
    /// <param name="propertyName">Zmemena vlastnost</param>
    protected virtual void OnPropertyChanged(string propertyName)
    {
        var handler = PropertyChanged;
        if ( handler != null )
        {
            handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    #endregion // Implementation of INotifyPropertyChanged

    #region | Implementation of IDisposable

    /// <summary>
    /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
    /// </summary>
    /// <filterpriority>2</filterpriority>
    public void Dispose()
    {
        this.OnDispose();
    }

    /// <summary>
    /// Potomci zde muzou provest "uklid" kodu - odebrani handleru, apod.
    /// </summary>
    protected virtual void OnDispose()
    {
    }

    #endregion // Implementation of IDisposable

    #region | Debug

#if DEBUG
    ~ViewModelBase()
    {
        string msg = string.Format("{0} ({1}) ({2}) Finalized", this.GetType().Name, this.DisplayName, this.GetHashCode());
        System.Diagnostics.Debug.WriteLine(msg);

    }
#endif
    #endregion // Debug
}

WorkspaceViewModel bude reprezentovat konkrétní záložku:

/// <summary>
/// Trida reprezentujici workspace - zobrazuje se jako zalozka
/// </summary>
public abstract class WorkspaceViewModel : ViewModelBase
{
    #region | Fields

    private RelayCommand _refreshCommand;
    private RelayCommand _closeCommand;

    #endregion // Fields

    #region | Commands

    /// <summary>
    /// Vrati kolekci Commandu, ktere budou dostupne v levem menu. K jednotlivym
    /// Commandum se pripoji jeden pro Refresh dat
    /// </summary>
    public List<RelayCommand> Commands
    {
        get
        {
            List<RelayCommand> cmds = new List<RelayCommand>()
            {
                RefreshCommand 
            };
            cmds.AddRange(OnCreateCommands());

            return cmds;
        }
    }

    protected virtual RelayCommand RefreshCommand
    {
        get
        {
            if ( _refreshCommand == null )
                _refreshCommand = new UICommand(param => Refresh())
                {
                    Name = Strings.Command_Refresh,
                };

            return _refreshCommand;
        }
    }

    public RelayCommand CloseCommand
    {
        get
        {
            if ( _closeCommand == null )
                _closeCommand = new UICommand(param => Close())
                {
                    Name = "x"
                };

            return _closeCommand;
        }
    }

    #endregion // Commands

    #region | Virtual Methods

    #region | CreateCommands

    /// <summary>
    /// Potomci si napisou vlastni implementaci
    /// </summary>
    /// <returns></returns>
    protected virtual List<RelayCommand> OnCreateCommands()
    {
        return new List<RelayCommand>();
    }

    public List<RelayCommand> CreateCommands()
    {
        return OnCreateCommands();
    }

    #endregion // CreateCommands

    #region | Initialize

    /// <summary>
    /// Potomci provedou vlastni obsluhu inicializacni metody
    /// </summary>
    protected virtual void OnInitialize()
    {
        // slouzi pro aktualizaci dat ve ViewModelu
    }

    public void Initialize()
    {
        this.OnInitialize();
    }

    #endregion // Initialize

    #region | Close

    public void Close()
    {
        this.OnRequestClose();
    }

    #endregion // Close

    #endregion // Virtual Methods

    #region | Overrides

    protected override void OnDispose()
    {
    }

    #endregion // Overrides

    #region | Private Methods

    #region | Refresh

    /// <summary>
    /// Provede aktualizaci  dat ve ViewModelu
    /// </summary>
    void Refresh()
    {
        this.OnInitialize();
    }

    #endregion // Refresh

    #region | OnRequestClose

    /// <summary>
    /// Vyvola udalost na uzavreni Workspace-u
    /// </summary>
    void OnRequestClose()
    {
        var handler = RequestClose;
        if ( handler != null )
            handler(this, EventArgs.Empty);
    }

    #endregion // OnRequestClose

    #endregion // Private NMethods

    #region | Events

    /// <summary>
    /// Udalost uzavreni workspace-u
    /// </summary>
    public event EventHandler RequestClose;

    #endregion // Events
}

WorkspaceViewModel obsahuje dva commandy, jeden pro uzavření workspace-u (Close) a druhý po aktualizaci dat (Refresh). Dále zde máme metodu CreateCommands, která vrátí vždy příslušné Commandy (Action Commands), které jsou dostupné v rámci každé záložky.

Commands

Jak jste si mohli všimnout tak ve třídě WorkspaceViewModel využíváme třídu RelayCommand a UICommand o které jsme předtím nemluvili. Nyní na to máme prostor.

image

Třída UICommand obsahuje pouze název commandu, delegát pro vykonání akce a podmínku, za které lze akci spustit.

RelayCommand

public abstract class RelayCommand : ICommand
{
    #region | Fields

    private readonly Action< object > _execute;
    private readonly Predicate< object > _canExecute;

    #endregion // Fields

    #region | Constructors

    /// <summary>
    /// Creates a new command that can always execute.
    /// </summary>
    /// <param name="execute">The execution logic.</param>
    protected RelayCommand(Action<object> execute)
        : this(execute, null)
    {
    }

    /// <summary>
    /// Creates a new command.
    /// </summary>
    /// <param name="execute">The execution logic.</param>
    /// <param name="canExecute">The execution status logic.</param>
    protected RelayCommand(Action<object> execute, Predicate<object> canExecute)
    {
        if ( execute == null )
            throw new ArgumentNullException("execute");

        _execute = execute;
        _canExecute = canExecute;
    }

    #endregion

    #region | ICommand members

    [DebuggerStepThrough]
    public bool CanExecute(object parameter)
    {
        return _canExecute == null ? true : _canExecute(parameter);
    }

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

    public virtual void Execute(object parameter)
    {
        if ( CanExecute(parameter) )
        {
            _execute(parameter);
        }
    }

    #endregion // ICommand Members

    #region | Properties

    /// <summary>
    /// Nazev Commandu - zobrazi se na tlacitku
    /// </summary>
    public string Name { get; set; }

    #endregion // Properties
}

A UICommand

/// <summary>
/// Spusti command na UI vlakne
/// </summary>
public class UICommand : RelayCommand
{
    public UICommand(Action<object> execute)
        : base(execute)
    {
    }

    public UICommand(Action<object> execute, Predicate<object> canExecute)
        : base(execute, canExecute)
    {
    }
}

Příště si vytvoříme hlavní aplikaci (MVVMSample.Shell) kde bude MainWindowViewModel, který bude představovat naše hlavní okno aplikace. Jakmile si připravíme tohle tak pak začneme vytvářet jednotlivé moduly a skládat vše dokupy.

Tags:

MVVM #1–Úvod

by Lukáš Kubis února 13, 2011 07:15

Nedávno jsem vyvíjel jednu aplikaci, která mě inspirovala k napsání následujících článků. Bude se jednat především o použití MVVM ve WPF aplikaci, ale pokusím se zde zahrnout i věci týkající se MEFu, Expression Blendu, či něčeho jiného co mi příjde zajímavé.

Pro ukázku si poskládáme jednoduchou WPF aplikaci od základů až po různá vylepšení. Tato aplikace bude takovou odlehčenou verzi té původní, která mě inspirovala pro tento “seriál”. Na obrázku níže můžete vidět architekturu naší aplikace.

image

Aplikace se skládá z několika částí:

  • MVVMSample.Shell – Výsledná aplikace (exe), která po spuštění nabídne funkcionalitu jednotlivých modulů
  • MVVMSample.Shared – sdílená knihovna – obsahuje různé pomocné metody, společné třídy apod.
  • MVVMSample.ModuleA-C – jednotlivé moduly, kde každý modul představuje jednoduchou funkcionalitu

Tags:

Windows Phone 7–jak na orientaci

by Lukáš Kubis prosince 29, 2010 09:19

Vítejte u druhého dílu ze seriálu o vývoji pod WP7. Předchozí díl najdete na:

Dnes se ve velice krátké ukázce podíváme na možnosti orintace telefonu. Jak umožnit zobrazovat obsah v “portrait” nebo “landscape” módu, jak reagovat na události změny orientace ,apod.

Defaultně se aplikace spouštějí v tzv. “portrait” módu:

image

a pokud otočíme telefonem o 90° dostaneme se do tzv. “landscape” módu

image

Bohužel jak si můžete všimnout tak v landscape módu naše aplikace nevypadá zrovna nejlíp. To je způsobeno nastavením vlastnosti SupportedOrientations přímo v xamlu

SupportedOrientations="Portrait"

Tato vlastnost může nabývat těchto hodnot:

  • Portrait
  • Landscape
  • PortraitOrLandscape

Pokud vybereme poslední možnost PortraitOrLandscape, tak při natočení telefonu, se rovněž změní i layout aplikace

image

Víme co je nutné nastavit, aby byly podporovány oba módy zobrazení, ale jak např. zareagovat na změnu orientace. Představte si, že napíšete aplikaci, která může pracovat v obou módech a v závislosti na móodu se bude UI přizpůsobovat.

Třída PhoneApplicationBase obsahuje virtuální metodu OnOrientationChanged, kterou můžeme přepsat a tak jednoduše reagovat na změnu orientace.

public partial class MainPage : PhoneApplicationPage
{
    // Constructor
    public MainPage()
    {
        InitializeComponent();

        txt.Text = "Orientation: " + Orientation.ToString();
    }

    protected override void OnOrientationChanged(OrientationChangedEventArgs args)
    {
        txt.Text = "Orientation: " + Orientation.ToString();
        base.OnOrientationChanged(args);
    }
}

Vlastnost Orientation je typu PageOrientation a může nabývat následujících hodnot:

  • Landscape
  • LandscapeLeft
  • LandscapeRight
  • None
  • Portrait
  • PortraitDown
  • PortraitUp

image

Naviděnou u dalšího dílu

Tags: , ,

Silverlight 4 - Kurzovní lístek – krok za krokem

by Lukáš Kubis prosince 27, 2010 09:49

V dnešním článku si vytvoříme jednoduchou aplikaci, která bude zobrazovat aktuální kurzovní lístek. Tuto aplikaci si pak budete moci umístit např. na Vaše vlastní stránky.

1. krok – Návrh aplikace

Jako zdroj dat budeme využívat kurzovní lístek Československé obchodní banky, a.s. (ČSOB), který najdeme k dispozici ve formátu txt na adrese http://www.csob.cz/webcsob/kurzy/kurzynewcz.txt. Struktura souboru vypadá následovně:

2010-12-23 17:20:00

;;;;Devizy;;;Valuty
Země;Množství;Měna;Změna;Nákup;Prodej;Střed;Nákup;Prodej;Střed
Austrálie;1;AUD;0,80;18,953;19,687;19,320;0,00;0,00;0,00
Dánsko;1;DKK;0,20;3,330;3,460;3,395;3,32;3,46;3,39
EMS;1;EUR;0,20;24,817;25,779;25,298;24,77;25,83;25,30
Chorvatsko;1;HRK;0,10;3,358;3,488;3,423;0,00;0,00;0,00
Japonsko;100;JPY;1,20;22,744;23,672;23,208;0,00;0,00;0,00
Kanada;1;CAD;0,80;18,697;19,421;19,059;0,00;0,00;0,00
Maďarsko;100;HUF;0,50;8,976;9,342;9,159;0,00;0,00;0,00
Norsko;1;NOK;0,10;3,162;3,284;3,223;3,15;3,29;3,22
Polsko;1;PLN;0,50;6,217;6,471;6,344;0,00;0,00;0,00
Rumunsko;1;RON;0,30;5,789;6,025;5,907;0,00;0,00;0,00
Rusko;100;RUB;0,70;61,783;64,177;62,980;0,00;0,00;0,00
Švédsko;1;SEK;0,00;2,762;2,870;2,816;2,76;2,88;2,82
Švýcarsko;1;CHF;1,00;19,883;20,653;20,268;19,84;20,70;20,27
Turecko;1;TRY;0,70;12,199;12,671;12,435;0,00;0,00;0,00
USA;1;USD;0,40;18,908;19,680;19,294;18,87;19,71;19,29
Velká Británie;1;GBP;0,00;29,154;30,344;29,749;29,10;30,40;29,75

Vytvoříme si třídu, která bude reprezentovat aktuální kurz:

/// <summary>
/// Kurz
/// </summary>
public class ExchangeRate
{
    /// <summary>
    /// Měna
    /// </summary>
    public string Currency { get; set; }

    /// <summary>
    /// Země
    /// </summary>
    public string Country { get; set; }
        
    /// <summary>
    /// Množství
    /// </summary>
    public int Amount { get; set; }

    /// <summary>
    /// Změna
    /// </summary>
    public float Change { get; set; }

    /// <summary>
    /// Devizy - nákup
    /// </summary>
    public float PurchaseCashless { get; set; }

    /// <summary>
    /// Devizy - prodej
    /// </summary>
    public float SalesCashless { get; set; }

    /// <summary>
    /// Devizy - střed
    /// </summary>
    public float MidPointCashless { get; set; }

    /// <summary>
    /// Valuty - nákup
    /// </summary>
    public float PurchaseCash { get; set; }

    /// <summary>
    /// Valuty - prodej
    /// </summary>
    public float SalesCash { get; set; }

    /// <summary>
    /// Valuty - střed
    /// </summary>
    public float MidPointCash { get; set; }

    /// <summary>
    /// Graf
    /// </summary>
    public string Chart { get; set; }
}

Máme třídu reprezentující konkrétní kurz a ještě si vytvoříme třídu, která bude obsahovat až n-kurzů

/// <summary>
/// Kurzy
/// </summary>
public class ExchangeRates
{
    /// <summary>
    /// Seznam jednotlivych kurzu
    /// </summary>
    public ObservableCollection<ExchangeRate> Data { get; set; }
}

Máme data, potřebné třídy a tak už jenom stačí data stáhnout, inicializovat třídy a nějak pěkně zobrazit.

2. krok – Vytvoření WCF služby pro stažení dat z internetu

Pro stažení dat využijeme WCF službu, kde pomocí třídy WebClient a metody DownloadStringAsync stáhneme kurzovní lístek.

Poznámka: Jelikož server csob.cz neobsahuje potřebný soubor(y) crossdomain.xml a clientaccesspolicy.xml využívám pro stažení WCF službu. Kdybych použil WebClient.DownloadStringAsync přímo ze Silverlightu, dostal bych vždy SecurityException. Více o bezpečnostních omezeních najdete v článku Network Security Access Restrictions in Silverlight

WCF služba bude obsahovat pouze jednu metodu nazvanou GetData, která vrátí obsah kurzovního lístku. POZOR !!!tato WCF služba je umístěna ve webové aplikaci, ve které spuštíme silverlight aplikaci.

[ServiceContract]
public interface IExchangeRatesService
{
    [OperationContract]
    string GetData();
}

public class ExchangeRatesService : IExchangeRatesService
{
    public string GetData()
    {
        string data = null;
        using ( WebClient client = new WebClient() )
        {
            try
            {
                data = client.DownloadString(@"http://www.csob.cz/webcsob/kurzy/kurzynewcz.txt");
            }
            catch ( Exception ex )
            {
                data = ex.Message;
            }
        }
        return data;
    }
}

Poznámka: Url adresa kurzovního lístku je zde napevno, což není nejlepší řešení, takže bych spíše doporučil tuto adresu předávat jako parametr metody GetData. V naší ukázce to ale ničemu vadit nebude.

Upravíme ještě nastavení služby ve web.config-u

<system.serviceModel>
    <services>
      <service name="ExchangeRatesSample.Web.ExchangeRatesService">
        <endpoint address=""
                  binding="basicHttpBinding"
                  contract="ExchangeRatesSample.Web.IExchangeRatesService">
        </endpoint>
      </service>
    </services>
      <behaviors>
          <serviceBehaviors>
              <behavior>
                  <serviceMetadata httpGetEnabled="true" />
                  <serviceDebug includeExceptionDetailInFaults="true" />
              </behavior>
          </serviceBehaviors>
      </behaviors>
      <serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
  </system.serviceModel>

3. krok – Stažení dat pomocí WCF služby

V silverlight aplikaci přidáme referenci na WCF službu kliknutím pravým na References a vybráním volby “Add Service Reference”. Jakmile přidáme referenci, tak si otevřeme kód na pozadí MainPagexaml.cs a obsloužíme událost Loaded.

public partial class MainPage : UserControl
{
    public MainPage()
    {
        InitializeComponent();
        this.Loaded += MainPageOnLoaded;
    }

    void MainPageOnLoaded(object sender, RoutedEventArgs args)
    {
        var proxy = new ExchangeRatesServiceClient();
        proxy.GetDataCompleted += GetDataOnCompleted;
        proxy.GetDataAsync();
    }

    void GetDataOnCompleted(object sender, GetDataCompletedEventArgs args)
    {
        if ( args.Error == null && !args.Cancelled )
        {
            var rates = new ExchangeRates();
            rates.Data = new ObservableCollection< ExchangeRate >();
                
            var lines = from l in args.Result.Split('\r', '\n').Skip(8)
                        where !string.IsNullOrWhiteSpace(l)
                        select l;

            foreach ( var line in lines ) 
            {
                var rate = ExchangeRate.FromLine(line);
                if ( rate != null )
                    rates.Data.Add(rate);
            }
        }
    }
}

Třída ExchangeRatesServiceClient se mi vygenerovala, když jsem přidal referenci na WCF službu. V metodě GetDataOnCompleted si ziskám pouze ty řádky, na kterých jsou kurzy a převedu je na objekty typu ExchangeRate pomocí pomocné statické metody FromLine

public static ExchangeRate FromLine(string line)
{
    var data = line.Split(';');

    return new ExchangeRate()
    {
        Country = data[0],
        Amount = int.Parse(data[1]),
        Currency = data[2],
        Change = float.Parse(data[3]),
        PurchaseCashless = float.Parse(data[4]),
        SalesCashless = float.Parse(data[5]),
        MidPointCashless = float.Parse(data[6]),
        PurchaseCash = float.Parse(data[7]),
        SalesCash = float.Parse(data[8]),
        MidPointCash = float.Parse(data[9])
    };
}

4. krok – Zobrazení dat v DataGridu

Přidáme si do MainPage DataGrid

 <data:DataGrid Grid.Row="1" ItemsSource="{Binding Data}" 
                AutoGenerateColumns="True"/>

kde data je definice namespace ve kterém se DataGrid nachází (xmlns:data="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"). No a pak už jenom stačí za cyklem foreach nastavit DataContext na nově vytvořený objekt rates, který ve vlastnosti Data uchovává veškeré kurzy. Po spuštění může aplikace vypadat takto:

image

5. Krok – Úprava hlavičky DataGridu

Aplikace funguje – data se stáhnou, zobrazí, ale není to ještě úplně ono. Např. některé sloupce jsou zde uvedeny 2x - prodej, nákup a střed. První trojice je pro Devizy a druhá pro Valuty, ale zatím to poznám pouze já. Upravíme tedy hlavičku našeho gridu tak, aby měla “2 řádky”. Druhý řádek bude obsahovat zmiňované sloupce, ale první bude roztažen přes 3 a bude obsahovat konkrétní nápis buď Devizy nebo Valuty.

<Style x:Key="datagridHeaderStyle" TargetType="primitives:DataGridColumnHeader">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="primitives:DataGridColumnHeader">
                <Grid x:Name="Root">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition/>
                        <ColumnDefinition Width="Auto"/>
                    </Grid.ColumnDefinitions>
                    <VisualStateManager.VisualStateGroups>
                        <VisualStateGroup x:Name="CommonStates">
                            <VisualState x:Name="Normal"/>
                            <VisualState x:Name="MouseOver">
                                <Storyboard>
                                    <ColorAnimation Duration="0" 
                                            Storyboard.TargetName="BackgroundRectangle" 
                                            Storyboard.TargetProperty="(Fill).Color" To="#FF448DCA"/>
                                    <ColorAnimation Duration="0" 
                                            Storyboard.TargetName="BackgroundGradient" 
                                            Storyboard.TargetProperty="(Fill).(GradientStops)[3].Color" To="#7FFFFFFF"/>
                                    <ColorAnimation Duration="0" 
                                            Storyboard.TargetName="BackgroundGradient" 
                                            Storyboard.TargetProperty="(Fill).(GradientStops)[2].Color" To="#CCFFFFFF"/>
                                    <ColorAnimation Duration="0" 
                                            Storyboard.TargetName="BackgroundGradient" 
                                            Storyboard.TargetProperty="(Fill).(GradientStops)[1].Color" To="#F2FFFFFF"/>
                                </Storyboard>
                            </VisualState>
                            <VisualState x:Name="Pressed">
                                <Storyboard>
                                    <ColorAnimation Duration="0" 
                                            Storyboard.TargetName="BackgroundRectangle" 
                                            Storyboard.TargetProperty="(Fill).Color" To="#FF448DCA"/>
                                    <ColorAnimation Duration="0" 
                                            Storyboard.TargetName="BackgroundGradient" 
                                            Storyboard.TargetProperty="(Fill).(GradientStops)[0].Color" To="#D8FFFFFF"/>
                                    <ColorAnimation Duration="0" 
                                            Storyboard.TargetName="BackgroundGradient" 
                                            Storyboard.TargetProperty="(Fill).(GradientStops)[1].Color" To="#C6FFFFFF"/>
                                    <ColorAnimation Duration="0" 
                                            Storyboard.TargetName="BackgroundGradient" 
                                            Storyboard.TargetProperty="(Fill).(GradientStops)[2].Color" To="#8CFFFFFF"/>
                                    <ColorAnimation Duration="0" 
                                            Storyboard.TargetName="BackgroundGradient" 
                                            Storyboard.TargetProperty="(Fill).(GradientStops)[3].Color" To="#3FFFFFFF"/>
                                </Storyboard>
                            </VisualState>
                        </VisualStateGroup>
                        <VisualStateGroup x:Name="SortStates">
                            <VisualState x:Name="Unsorted"/>
                            <VisualState x:Name="SortAscending" />
                            <VisualState x:Name="SortDescending" />
                        </VisualStateGroup>
                    </VisualStateManager.VisualStateGroups>
                    <Rectangle x:Name="BackgroundRectangle" Fill="#FF1F3B53" Stretch="Fill" Grid.ColumnSpan="2"/>
                    <Rectangle x:Name="BackgroundGradient" Stretch="Fill" Grid.ColumnSpan="2">
                        <Rectangle.Fill>
                            <LinearGradientBrush EndPoint=".7,1" StartPoint=".7,0">
                                <GradientStop Color="#FCFFFFFF" Offset="0.015"/>
                                <GradientStop Color="#F7FFFFFF" Offset="0.375"/>
                                <GradientStop Color="#E5FFFFFF" Offset="0.6"/>
                                <GradientStop Color="#D1FFFFFF" Offset="1"/>
                            </LinearGradientBrush>
                        </Rectangle.Fill>
                    </Rectangle>
                    <Grid HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
                            VerticalAlignment="{TemplateBinding VerticalContentAlignment}">
                    <Grid.RowDefinitions>
                        <RowDefinition Height="20"/>
                        <RowDefinition Height="1"/>
                        <RowDefinition Height="20"/>
                    </Grid.RowDefinitions>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="50"/>
                        <ColumnDefinition Width="1"/>
                        <ColumnDefinition Width="50"/>
                        <ColumnDefinition Width="1"/>
                        <ColumnDefinition Width="50"/>
                    </Grid.ColumnDefinitions>
                    <!-- Row 0 -->
                    <ContentPresenter Content="{TemplateBinding Content}"
                                        VerticalAlignment="Center"
                                        HorizontalAlignment="Center"
                                        Grid.ColumnSpan="5"/>
                    <!-- Row 1 -->
                    <Rectangle Fill="#FFC9CACA" VerticalAlignment="Stretch" Height="1" 
                                Visibility="Visible" Grid.Row="1" Grid.ColumnSpan="5" />
                    <!-- Row 2 -->
                    <TextBlock Text="Purchase" VerticalAlignment="Center"
                                HorizontalAlignment="Center" Grid.Row="2"/>
                    <Rectangle Fill="#FFC9CACA" VerticalAlignment="Stretch" Width="1" 
                                Visibility="Visible" Grid.Row="2" Grid.Column="1"/>
                    <TextBlock Text="Sales" VerticalAlignment="Center"
                                HorizontalAlignment="Center" Grid.Row="2" Grid.Column="2"/>
                    <Rectangle Fill="#FFC9CACA" VerticalAlignment="Stretch" Width="1" 
                                Visibility="Visible" Grid.Row="2" Grid.Column="3"/>
                    <TextBlock Text="MidPoint" VerticalAlignment="Center"
                                HorizontalAlignment="Center" Grid.Row="2" Grid.Column="4"/>
                </Grid>
                <Rectangle x:Name="VerticalSeparator" Fill="#FFC9CACA" 
                            VerticalAlignment="Stretch" Width="1" Visibility="Visible" 
                            Grid.Row="1" Grid.Column="1"/>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

Poznámka: Informace o tom jak upravit hlavičku datagridu jsem čerpal z následující stránky

Ve stručnosti vysvětleno, jde o vytvoření gridu se 3 řádky a 5 sloupci. Kde v prvním řádku je hlavička (devizy nebo valuty), ve druhém oddělovač a v posledním hodnoty Prodej, oddělovač, nákup, oddělovač a střed (proto 5 sloupců). Upravíme tedy datagrid aby negenoravl sloupečky automaticky, ale nechal to na našem manuálním vytvoření

<data:DataGrid Grid.Row="1" ItemsSource="{Binding Data}" 
                AutoGenerateColumns="False" IsReadOnly="True">
    <data:DataGrid.Columns>
        <data:DataGridTextColumn Header="Currency" 
                                    Binding="{Binding Currency}"/>
        <data:DataGridTextColumn Header="Country" 
                                    Binding="{Binding Country}"/>
        <data:DataGridTextColumn Header="Amount" 
                                    Binding="{Binding Amount}"/>
        <data:DataGridTextColumn Header="Change" 
                                    Binding="{Binding Change, StringFormat=P}"/>
        <data:DataGridTemplateColumn Header="Cashless" HeaderStyle="{StaticResource datagridHeaderStyle}">
            <data:DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal">
                        <TextBlock Text="{Binding PurchaseCashless}" VerticalAlignment="Center"
                                HorizontalAlignment="Center" Width="50"/>
                        <Rectangle Fill="#FFC9CACA" VerticalAlignment="Stretch" Width="1" 
                                Visibility="Visible"/>
                        <TextBlock Text="{Binding SalesCashless}" VerticalAlignment="Center"
                                HorizontalAlignment="Center" Width="50"/>
                        <Rectangle Fill="#FFC9CACA" VerticalAlignment="Stretch" Width="1" 
                                Visibility="Visible"/>
                        <TextBlock Text="{Binding MidPointCashless}" VerticalAlignment="Center"
                                HorizontalAlignment="Center" Width="50"/>
                    </StackPanel>
                </DataTemplate>
            </data:DataGridTemplateColumn.CellTemplate>
        </data:DataGridTemplateColumn>
        <data:DataGridTemplateColumn Header="Cash" HeaderStyle="{StaticResource datagridHeaderStyle}">
            <data:DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal">
                        <TextBlock Text="{Binding PurchaseCash}" VerticalAlignment="Center"
                                HorizontalAlignment="Center" Width="50"/>
                        <Rectangle Fill="#FFC9CACA" VerticalAlignment="Stretch" Width="1" 
                                Visibility="Visible"/>
                        <TextBlock Text="{Binding SalesCash}" VerticalAlignment="Center"
                                HorizontalAlignment="Center" Width="50"/>
                        <Rectangle Fill="#FFC9CACA" VerticalAlignment="Stretch" Width="1" 
                                Visibility="Visible"/>
                        <TextBlock Text="{Binding MidPointCash}" VerticalAlignment="Center"
                                HorizontalAlignment="Center" Width="50"/>
                    </StackPanel>
                </DataTemplate>
            </data:DataGridTemplateColumn.CellTemplate>
        </data:DataGridTemplateColumn>
        <data:DataGridTextColumn Header="Chart" 
                                 Binding="{Binding Chart}"
			      Width="*"/>
    </data:DataGrid.Columns>
</data:DataGrid>

Většina sloupečků je typu TextColumn, protože nám stačí zobrazení pouhého textu, ale u hodnot pro Devizy a Valuty je nutné použít TemplateColumn. Pomocí DataTemplate vytvoříme StackPanel, obsahující 3x Textbox (Nákup, Prodej, Střed) oddělený oddělovačem (podobně jako v definici stylu)

image

Poznámka: U posledního sloupce Chart nastavíme šířku pomocí hvězdičky, což znamená, že se sloupec roztáhne až do konce celého datagridu.

6. krok – Přidání odkazu

Aby naše aplikace byla dle našich představ je potřeba přidat odkaz u sloupce Country, tzn. když uživatel klikne na název země, aby došlo k přesměrování na stránky s detailními infromacemi o měně. Pro tuto funkcionalitu si vytvoříme vlastní DetailLinkButton. Je potřeba nastavit NavigateUri na odkaz dle aktuální měny, která se předává jako parametr.

public class DetailLinkButton : HyperlinkButton
{
    private const string BaseCsUri = "http://www.csob.cz/cz/Csob/Kurzovni-listky/Stranky/kurzovni-listek-detail.aspx?Currency=AUD={0}";

    public static readonly DependencyProperty CurrencyProperty =
        DependencyProperty.Register("Currency", typeof(string),
        typeof(DetailLinkButton), null);

    public DetailLinkButton()
    {
        Click += DetailLinkOnClick;
    }

    void DetailLinkOnClick(object sender, RoutedEventArgs e)
    {
        TargetName = "_blank";
        NavigateUri = new Uri(string.Format(BaseCsUri, Currency));
    }

    public string Currency
    {
        get { return GetValue(CurrencyProperty).ToString(); }
        set { SetValue(CurrencyProperty, value); }
    }
}

Stačí už jenom upraviz xaml, aby místo DataGridTextColumn používal DetailLinkButton

<data:DataGridTemplateColumn Header="Country">
    <data:DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
            <local:DetailLinkButton Currency="{Binding Currency}"
                                    Content="{Binding Country}"/>
        </DataTemplate>
    </data:DataGridTemplateColumn.CellTemplate>
</data:DataGridTemplateColumn>

Do Currency si načteme kód měny a jako obsah odkazu zůstane název země. Podobně se změní i sloupec pro graf, kde nyní použijeme ikonu

<data:DataGridTemplateColumn Header="Chart" Width="*">
    <data:DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
            <local:DetailLinkButton Currency="{Binding Currency}">
                <Image Source="Images/ico-graf.png" Stretch="Fill" Width="16" Height="16"/>
            </local:DetailLinkButton>
        </DataTemplate>
    </data:DataGridTemplateColumn.CellTemplate>
</data:DataGridTemplateColumn>

7. krok – Přidání ikon

Jeden obrázek už v aplikaci máme a nyní přidáme ostatní. Nejdříve přidáme jednotlivé vlaječky k názvům měny. Tyto vlaječky si uložíme do složky Images

image

A k tomu aby jsme je zobrazily v gridu, bude potřeba vytvořit Converter, který na základě kódu měny vrátí správný název obrázku.

public class CurrencyToImageConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if ( value == null )
            return null;

        return string.Format("Images/{0}.png", value);
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

V xamlu pak stačí upravit pár řádků pro Currency

<data:DataGridTemplateColumn Header="Currency">
    <data:DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
            <StackPanel Orientation="Horizontal">
                <Image Source="{Binding Currency, Converter={StaticResource conv}}"
                        Width="16" Height="16" VerticalAlignment="Center"
                        HorizontalAlignment="Center" Margin="5"/>
                <TextBlock Text="{Binding Currency}"
                            VerticalAlignment="Center"
                            HorizontalAlignment="Center"
                            Margin="5"/>
            </StackPanel>
        </DataTemplate>
    </data:DataGridTemplateColumn.CellTemplate>
</data:DataGridTemplateColumn>

a Chart

<data:DataGridTemplateColumn Header="Chart" Width="*">
    <data:DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
            <local:DetailLinkButton Currency="{Binding Currency}" Margin="5">
                <Image Source="Images/ico-graf.png" Stretch="Fill" Width="16" Height="16"/>
            </local:DetailLinkButton>
        </DataTemplate>
    </data:DataGridTemplateColumn.CellTemplate>
</data:DataGridTemplateColumn>

V poslední řadě si pak vytvoříme konverte, který na základě změny (Change) zobrazí buď zelenou (kladné hodnoty), modrou (beze změny), červenou šipku (záporné hodnoty)

public class ChangeConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if ( value == null )
            return null;

        float change = float.Parse(value.ToString());
        if ( change > 0 )
            return "Images/arrow_up_green.png";
        else if ( change == 0 )
            return "Images/arrow_right_blue.png";
        else
            return "Images/arrow_down_red.png";
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

V xamlu bude úprava téměř totožná jako sloupeček s vlajkami.

<data:DataGridTemplateColumn Header="Change">
    <data:DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="{Binding Change, StringFormat=P}"
                            VerticalAlignment="Center" HorizontalAlignment="Center"
                            Margin="5"/>
                <Image Source="{Binding Change, Converter={StaticResource conv2}}"
                        Width="16" Height="16" HorizontalAlignment="Center"
                        VerticalAlignment="Center" Margin="5"/>
            </StackPanel>
        </DataTemplate>
    </data:DataGridTemplateColumn.CellTemplate>
</data:DataGridTemplateColumn>

image

8. krok – Lokalizace

Předposledním krokem bude lokalizace aplikace, která ať se Vám může zdát složitá je naopak velice jednoduchá. Přidáme si do aplikace Resources (Strings.resx)

image

A anglickou verzi Strings.en-US.resx

image

Aby jsme se dostali ze XAMLu na naše Resources, přidíme si instanci jako StaticResource

<local:CustomResources x:Key="LocStrings"/>

LocStrings je typu CustomResources. Jedná se o pomocnou třídu, která má vlastnost LocalizedStrings typu Strings(Resource) a slouží především proto, že když uživatel za běhu změní kulturu, tak abych mohl notifikovat UI o tom, že došlo ke změně a došlo k přečtení správného resource

public class CustomResources : INotifyPropertyChanged
{
    private readonly Resources.Strings _strings = new Resources.Strings();

    public Resources.Strings LocalizedStrings
    {
        get { return _strings; }
        set { OnPropertyChanged(MethodBase.GetCurrentMethod()); }
    }

    public event PropertyChangedEventHandler  PropertyChanged;

    private void OnPropertyChanged(MethodBase methodBase)
    {
        if ( PropertyChanged != null )
        {
            PropertyChanged(this, new PropertyChangedEventArgs(methodBase.Name.Substring(4)));
        }
    }
}

Nyní všechen text, který mám v XAMLu natvrdo nahradím části kódu, která načte příslušný string na základě zvolené kultury. Ukázka jak toho docílit např. pro měnu:

 <TextBlock Text="{Binding Source={StaticResource LocStrings}, Path=LocalizedStrings.Currency}"/>

Problém je, že toto nefunguje u vlastnosti Header, takže místo přeloženého textu se nám zobrazí System.Windows.Data.Binding. Je nutné to trošku objeít např. tímto způsobem

public class ExDataGridTextColumn : DataGridTextColumn
{
    public string ColumnHeader
    {
        get
        {
            return ( string ) GetValue(ColumnHeaderProperty);
        }
        set
        {
            SetValue(ColumnHeaderProperty, value);
        }
    }

    public static readonly DependencyProperty ColumnHeaderProperty =
        DependencyProperty.Register("ColumnHeader", typeof(string),
        typeof(ExDataGridTextColumn), new PropertyMetadata(ColumnHeaderChanged)
    );

    private static void ColumnHeaderChanged(object sender, DependencyPropertyChangedEventArgs args)
    {
        DataGridTextColumn dgc = sender as DataGridTextColumn;

        string h = (string) args.NewValue;
        if ( args.NewValue != args.OldValue )
        {
            dgc.Header = h;
        }
    }
}

public class ExDataGridTemplateColumn : DataGridTemplateColumn
{
    public string ColumnHeader
    {
        get
        {
            return ( string ) GetValue(ColumnHeaderProperty);
        }
        set
        {
            SetValue(ColumnHeaderProperty, value);
        }
    }

    public static readonly DependencyProperty ColumnHeaderProperty =
        DependencyProperty.Register("ColumnHeader", typeof(string),
        typeof(ExDataGridTemplateColumn), new PropertyMetadata(ColumnHeaderChanged)
    );

    private static void ColumnHeaderChanged(object sender, DependencyPropertyChangedEventArgs args)
    {
        ExDataGridTemplateColumn dgc = sender as ExDataGridTemplateColumn;

        string h = ( string ) args.NewValue;
        if ( args.NewValue != args.OldValue )
        {
            dgc.Header = h;
        }
    }
}

Vytvoříme si dvě třídy ExDataGridTextColumn a ExDataGridTemplateColumn, kde definujeme jednu vlastnost ColumnHeader. Tato vlastnost slouží jako náhrada vlastnosti Header (nyní lze už použít Binding). Můžeme tedy nastavit hlavičku sloupce jednoduchým způsobem:

ColumnHeader="{Binding Source={StaticResource LocStrings}, Path=LocalizedStrings.Cash}"

Na závěr už pak jenom přidáme ComboBox, ve kterém bude seznam dostupných lokalizací a uživatel si tak bude moct zvolit, kterou jazykovou verzi bude chtít využít.

<ComboBox x:Name="ui_cbLanguages" SelectionChanged="LanguagesOnSelectionChanged"
            HorizontalAlignment="Right" Grid.Row="0">
    <ComboBoxItem Tag="en-Us">
        <Image Source="Images/USD.png" Width="16" Height="16"/>
    </ComboBoxItem>
    <ComboBoxItem Tag="cs-CZ">
        <Image Source="Images/CZE.png" Width="16" Height="16"/>
    </ComboBoxItem>
</ComboBox>

No a v obsluze metody LanguagesOnSelectionChanged provedeme pouze změnu nastavení kultury

private void LanguagesOnSelectionChanged(object sender, SelectionChangedEventArgs args)
{
    ComboBoxItem item = ui_cbLanguages.SelectedItem as ComboBoxItem;
    string code = item.Tag.ToString();
    System.Threading.Thread.CurrentThread.CurrentCulture = new CultureInfo(code);
    System.Threading.Thread.CurrentThread.CurrentUICulture = new CultureInfo(code);
    // notifikuju o zmene
    (( CustomResources ) this.Resources["LocStrings"]).LocalizedStrings = new Resources.Strings();
}

9. krok – Izolované uložiště

Úplně poslední krok dnešního tutoriálu je ukázka jak využít izolované uložiště. Jakmile se aplikace spustí, stáhne data pomocí WCF služby a poté uloží stažené data do izolovaného uložiště. Při dalším spuštění aplikace ověří, zda již neexistují data a pokud existují a jsou platná, nahrají se z toho uložiště a není potřeba volat znovu WCF službu.

private void SaveData(string data, string path)
{
    using ( IsolatedStorageFile file = IsolatedStorageFile.GetUserStoreForApplication() )
    {
        using ( IsolatedStorageFileStream isolatedStorageFileStream = 
            new IsolatedStorageFileStream(path, FileMode.Create, file) )
        {
            using ( StreamWriter writer = new StreamWriter(isolatedStorageFileStream) )
            {
                writer.Write(data);
            }
        }
    }
}

public bool GetData(string path, out string data)
{
    data = string.Empty;

    using ( IsolatedStorageFile file = IsolatedStorageFile.GetUserStoreForApplication() )
    {
        if ( !file.FileExists(path) )
            return false;

        using ( IsolatedStorageFileStream isolatedStorageFileStream = 
            new IsolatedStorageFileStream(path, FileMode.OpenOrCreate, file) )
        {
            using ( StreamReader reader = new StreamReader(isolatedStorageFileStream) )
            {
                string dateTimeLine = reader.ReadLine();
                DateTime lastDateTime = DateTime.Parse(dateTimeLine);
                if ( lastDateTime.Year != DateTime.Now.Year ||
                        lastDateTime.DayOfYear != DateTime.Now.DayOfYear )
                    return false;

                reader.BaseStream.Position = 0;
                data = reader.ReadToEnd();
            }
        }
    }
    return true;
}

A kontrola v Load události

void MainPageOnLoaded(object sender, RoutedEventArgs args)
{
    // podivam se do izolovaneho uloziste
    string data =string.Empty;
    if ( !GetData("kurzy.txt", out data) )
    {
        var proxy = new ExchangeRatesServiceClient();
        proxy.GetDataCompleted += GetDataOnCompleted;
        proxy.GetDataAsync();
    }
    else
    {
        FillData(data);
    }
}

Tááák a to je pro tuto ukázku vše. Pokud jse se dočetli až sem, tak jsem rád, protože já sám měl problém to do takových rozměrů sepsat Smile

Ukázkovou aplikaci lze stáhnout na adrese http://lukaskubis.net/Demos/ExchangeRatesSample.zip

Tags:

Windows Phone 7 - začínáme

by Lukáš Kubis prosince 23, 2010 07:47

Dneska jsem se rozhodl, že se podívám trošku pod pokličku Windows Phone 7 (dále jen WP7) a tak tady budu sepisovat jednotlivé články na různá témata. Nejdříve zde přidám pár zajímavých odkazů, které je dobré znát

  • ScottGu’s Blog – oznámení o release verze WP7 Developer Tools (obsahuje základní informace + odkazy na další zajimavé stránky věnující se tomuto tématu)
  • MSDN – WP7 Training Course (výukový kurz) – lze stáhnout i v offline verzi
  • Channel 9 – Kurz pro absolutní začátečníky
  • E-book – Elektronická kniha o Programování WP 7 zdarma od Charlesa Petzolda

Než se pustím do psaní o WP7, rád bych zde udržoval seznam již publikovaných článků:

  1. WP7 – začínáme
    • Seznámení s WP7 + vytvoření HelloWorld aplikace
  2. WP7 – jak na orientaci
    • Podporované orientace, reakce na změnu orientace
  3. WP7 -

Na WP7 již bylo napsáno několik recenzí, tak se pojďme podívat jenom v krátkosti, jak vůbec vypadá, co obsahuje apod.

Tlačítka

Jak můžeme vidět úplně dole se nachází pouze tři tlačítka

  • Zpět – toto tlačítka má podobnou funkcionalitu jako např. tlačítko zpět v prohlížeči, tedy návrat na předchozí stránku. Pokud se ovšem nacházíme na úvodní stránce aplikace, tak po stisku tohoto tlačítka dojde k ukončení aplikace
  • Start – zobrazí uživateli nabídku start
  • Hledat – slouží pro hledání

Displej

V první verzi je k dispozici displej o rozměrech 480 x 800 pixelů. V budoucí verzi je naplánovaná podpora i pro rozlišení 320 x 480 pixelů.

Multi-touch

Displej je schopný rozpoznat ovládání lidským prstem, ale nezvládne už ovládání pomocí stylusu, či jiné “pomůcky”.

Sensory a služby

  • Wi-Fi
  • GPS
  • Fotoaparát
  • FM rádio
  • Akcelerometr – reaguje na pohyb telefonu

Poznámka: Informace výše o telefonu pocházejí z různých zdrojů. Bohužel se mi ještě nepodařilo dostat telefon do svých rukou.

Vytváříme nový projekt

Pokud máme nainstalované Windows Phone 7 Developer Tools, tak při výběru nového projektu máme možnost, zda si vytvoříme aplikaci v silverlightu, nebo pomocí XNA.

Silverligh for Windows Phone

Silverligh for Windows Phone

XNA Game Studio 4.0

XNA Game Studio 4.0

My se momentálně budeme zabývat pouze vývojem Silverlight aplikací. Vybereme tedy Silverlight Phone Application a pojmenujeme si jí jak jinak, než HelloWorld Smile. Po vytvoření projektu se vytvoří základní struktura s potřebnými soubory. Pojďme se na ní podívat:

  • HelloWorld – název projektu
    • [Properties] – název složky
      • AppManifest.xml – momentálně "prázdný"
      • AssemblyInfo.cs – informace o assembly
      • WMAppManifest.xml – obsahuje následující informace
        • Název, autor, popis, vydavatel aplikace
        • Název ikony
        • Url adresa obrázku pro pozadí
        • název úvodní stránky
        • apod.
    • App.xaml – definuje pouze události Lanching, Closing, Activated, Deactivated objektu PhoneApplicationService
    • App.xaml.cs – obsluha událostí, inicializace komponent, odchycení neodchycených výjimek a další nutné operace pro spuštění aplikace
    • MainPage.xaml – úvodní stránka s obsahem, která se zobrazí po spuštění aplikace
    • MainPage.xaml.cs – Kód na pozadí (obsluha událostí, inicializace, apod.)
    • ApplicationIcon.png – ikona aplikace
    • Background.png – obrázek pozadí aplikace
    • SplashScreenImage.jpg – obrázek, který se zobrazí během spouštění aplikace

Než provedeme spuštění aplikace, musíme si ověřit, že máme nastavený WP7 Emulator. V případě, že by jsme měli nastavený WP7 Device, aplikace by se nahrála přímo do našeho zařízení. Pro testovací účely necháme vybranou možnost WP7 Emulator. První spuštění bude trvat kapánek déle a po nějaké chvíli se nám objeví emulátor WP7 se spuštěnou aplikací.

image

Jakmile aplikaci ukončíme, můžeme si všimnout, že emulátor zůstane spuštěný (pokud jsme ho nezavřeli pomocí křížku). Další následné spuštění už je o dost rychlejší.

Upravíme tedy aplikaci, aby aspoň někde zobrazovala Hello World. Upravíme MainPage.xaml tak, že zde přidáme jedno tlačítko s popisem Klikni

<StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
    <TextBlock x:Name="ApplicationTitle" Text="Hello World Sample" Style="{StaticResource PhoneTextNormalStyle}"/>
    <TextBlock x:Name="PageTitle" Text="page name:" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
    <Button Content="Klikni" Click="Button_Click"/>
</StackPanel>

Obsluha kliknutí na tlačítko

private void Button_Click(object sender, RoutedEventArgs e)
{
    PageTitle.Text = "Hello World!!!";
}

Naše aplikace po stisknutí tlačítka klikni změní text z "page name" na "Hello World!!!".

To je pro dnešní díl vše a v brzké době se můžete těšit na pokračování, kde si už trošku více zaprogramujeme.

Tags: , ,

Silverlight–vyvolání PropertyChanged události

by Lukáš Kubis prosince 19, 2010 09:56

Dnešní článek bude spíše takový sourn toho co jsem objevil na internetu.

Všichni, kdo vytváříte silverlight aplikace. jste již setkali s rozhraním INotifyPropertyChanged. “Otrocké” vytváření vlastností tímto způsobm bylo na denním pořadku:

Textový název vlastnosti

public string FirstName
{
    get { return _firstName; }
    set
    {
        _firstName = value;
        RaisePropertyChanged("FirstName");
    }
}

Avšak tento způsob není ideální, protože zde zadáváme název vlastnosti jako string a většina z nás využívá techniku “copy/paste” takže zde může dojít jednoduše k chybám. Další způsob, jak vyvolat událost PropertyChanged už je trochu lepší a to využitím lambda výrazů, ze kterého pak odvodíme název vlastnosti…

Lambda výraz

public string FirstName
{
    get { return _firstName; }
    set
    {
        _firstName = value;
        RaisePropertyChanged(()=>FirstName);
    }
}

…ovšem i zde, si nemůžeme být jistí, že neuděláme chybu a omylem nevyužijeme jinou vlastnost, např. LastName. Co když ale upravíme vlastnost FirstName tak, aby vypadala následovně

StackTrace

public string FirstName
{
    get { return _firstName; }
    set
    {
        _firstName = value;
        RaisePropertyChanged();
    }
}

Voláme zde RaisePropertyChanged, ale bez parametru! Tělo metody RaisePropertyChanged vypadá takhle

public virtual void RaisePropertyChanged()
{
    var frames = new System.Diagnostics.StackTrace();
    for (var i = 0; i < frames.FrameCount; i++)
    {
        var frame = frames.GetFrame(i).GetMethod() as MethodInfo;
        if (frame != null)
            if (frame.IsSpecialName && frame.Name.StartsWith("set_"))
            {
                RaisePropertyChanged(frame.Name.Substring(4));
                return;
            }
    }
    throw new InvalidOperationException("NotifyPropertyChanged() can only by invoked within a property setter.");
}

Pomocí vlastnosti StackTrace se dostanu na informaci, který setter sevolá (konvence pro setter je set_propertyName). Jednoduše se tedy zbavím prefixu set_ a zbytek použiju jako parametr při volání PropertyChanged události.

Informace výše jsem převzal z článku http://csharperimage.jeremylikness.com/2010/12/jounce-part-8-raising-property-changed.html

MethodBase.GetCurrentMethod()

Další způsob jak se vyhnout specifikování názvu vlastnosti je využít metody GetCurrentMethod, která vrátí název aktuálně prováděné operace. V případě setteru to bude set_FirstName.

public string FirstName
{
    get { return _firstName; }
    set
    {
        _firstName = value;
        RaisePropertyChanged(System.Reflection.MethodBase.GetCurrentMethod());
    }
}

Využíváte ještě jiný způsob notifikace? Podělte se s ostatními pomocí komentářů pod článkem.

Tags:

Silverlight–WebClient II.–zobrazení RSS

by Lukáš Kubis prosince 19, 2010 05:38

V minulém článku jsme si ukázali jak pomocí třídy WebClient stáhnout obsah souboru. Dnes si ukážeme možnost, jak v naší aplikaci zobrazit RSS kanál.

Design aplikace

Design aplikace je velice jednoduchý. Naše uživatelské rozhraní obsahuje pouze tlačítko, které po stisku zobrazí obsah jednoho RSS kanálu.

Pro testovací účely jsem si v mojí webové aplikaci vytvořil adresář Files, do kterého jsem umístil soubor posts.rss

Obsah RSS kanálu se zobrazuje pomocí ItemsControl, kde jsem si vytvořil vlastní šablonu aby výsledek vypadal aspoň trošku k světu:

<UserControl x:Class="WebClientViewRssSample.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    d:DesignHeight="300" d:DesignWidth="400">

    <Grid x:Name="LayoutRoot" Background="White">
        <Grid.RowDefinitions>
            <RowDefinition Height="40"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="100"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <Button Content="Zobrazit RSS" Click="ViewRssButtonOnClick" Margin="5"/>
        <TextBox Grid.Column="1" Grid.ColumnSpan="2" IsReadOnly="True" Text="../Files/posts.rss"
                   HorizontalAlignment="Left" VerticalAlignment="Center"/>
        <ScrollViewer
            Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2"
            HorizontalScrollBarVisibility="Disabled"
            VerticalScrollBarVisibility="Auto">
            <ItemsControl x:Name="ui_Data">
                <ItemsControl.ItemTemplate>
                    <DataTemplate>
                        <Border BorderBrush="Black" BorderThickness="1" Margin="5">
                            <StackPanel>
                                <TextBlock Text="{Binding Title}" FontWeight="Bold"/>
                                <TextBlock Text="{Binding Description}" TextWrapping="Wrap"/>
                            </StackPanel>
                        </Border>
                    </DataTemplate>
                </ItemsControl.ItemTemplate>
            </ItemsControl>
        </ScrollViewer>            
            
    </Grid>
</UserControl>

Jednoduše se pro každou položku RSS zobrazí rámeček s titulem (tučně) a jeho popisem.

Pro objektovou reprezentaci jednotlivých položek jsem si vytvořil třídu Post

public class Post
{
    public string Title { get; set; }
    public string Description { get; set; }
    public DateTime PubDate { get; set; }

    internal static Post FromFeedItem(SyndicationItem feedItem)
    {
        string description = string.Empty;

        if ( feedItem.Summary != null )
            description = feedItem.Summary.Text;
        else
            description = (( TextSyndicationContent ) feedItem.Content).Text;

        return new Post()
        {
            Title = feedItem.Title.Text,
            PubDate = feedItem.PublishDate.DateTime,
            Description = description
        };
    }
}

Třída obsahuje vlastnosti reprezentující Titul, popis a datum publikování. Kromě těchto základních vlastností obsahuje metodu FromFeedItem, která příjme objekt typu SyndicationItem (reprezentuje položku RSS kanálu), přečte potřebné hodnoty a vrátí mi můj objekt Post:

Odkud, ale dostanu SyndicationItem objekt? Ve jmenném prostoru System.ServiceModel.Syndication (knihovna System.ServiceModel.dll) se nachází třída SyndicationFeed, která reprezentuje náš RSS (Atom) kanál. Tato třída nabízí statickou metodu Load, která přebírá jako parametr objekt typu XmlReader, který má rovněž metodu Load a jedno její přetížení přebírá parametr typu Stream, který dostaneme po výsledku asynchronního volání pomocí třídy WebClient.

Zavolám tedy metodu pro asynchroní stažení:

_webClient.OpenReadAsync(new Uri("../Files/posts.rss", UriKind.Relative));

Z výsledku si získám Stream a vytvořím XmlReader,

 XmlReader reader = XmlReader.Create(args.Result);

který potom předám jako parametr metodě Load třídy SyndicationFeed

SyndicationFeed feed = SyndicationFeed.Load(reader);

Pak jednoduše pomocí LINQu si projdu všechny položky a zobrazím v UI

var query = from feedItem in feed.Items
                  select Post.FromFeedItem(feedItem);

ui_Data.ItemsSource = query;

Výsledek si můžete prohlédnout na testovací stránce: http://lukaskubis.net/Demos/Apps/WebClientViewRssSampleTestPage.html

Poznámka: Na testovací stránce používám jako zdroj dat posts.txt (nikoliv rss). Je to z důvodu, že rss není defaultně na IISku povolené a já si to momentálně sám poolit nemůžu, proto ta změna přípony.

Materiály ke stažení

Demo je ke stažení zde

Tags:

Powered by BlogEngine.NET 1.6.0.0
Theme by Mads Kristensen | Modified by Mooglegiant