2013. április 4., csütörtök

Wpf #6 Konverter

Az eddig megismertek felhasználásával már számos Model osztályt fel lehet építeni, ám rengeteg olyan WPF tulajdonság van, amit nem biztos, hogy célszerű az osztályban ugyanolyan típusúra deklarálni.

A legegyszerűbb talán egy adott elem Visibility tulajdonsága.
pl.: <Rectangle Visibility="Hidden"/>
A Visibility 3 értéket vehet fel: Hidden, Visible, Collapsed

Amennyiben  kódból kell az értékét változtatni úgy az alábbiak szerint kell eljárni:
MyRectangle.Visibility = Visibility.Hidden;

A Most következő példában a "Collapsed" érték mellőzésre kerül. A láthatóságot egy bool tulajdonság segítségével lehet szabályozni. (true=Visible, false=Hidden)

Ehhez egy nagyon egyszerű Model osztályra lesz szükség:
class Model : INotifyPropertyChanged
{

 private bool _rectVisibility;
 public bool RectVisibility
 {
  get { return _rectVisibility; }
  set { _rectVisibility = value; OnPropertyChanged("RectVisibility"); }
 }

 // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

 public event PropertyChangedEventHandler PropertyChanged;
 public void OnPropertyChanged(string propertyName)
 {
  if (PropertyChanged != null)
   PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
 }

 // ---------------------------------------------------------------------

 public Model(bool visibility)
 {
  RectVisibility = visibility;
 }


}

A Láthatóságot a konstruktoron keresztül célszerű alaphelyzetbe hozni.

A hozzá tartozó ViewModel is pont ugyanennyire egyszerű
class ViewModel : INotifyPropertyChanged
{
 private Model _myModel;
 public Model MyModel
 {
  get { return _myModel; }
  set { _myModel = value; OnPropertyChanged("MyModel"); }
 }

 public event PropertyChangedEventHandler PropertyChanged;
 public void OnPropertyChanged(string propertyName)
 {
  if (PropertyChanged != null)
   PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
 }

 public ViewModel()
 {
  MyModel = new Model(true);
 }
}

A View kód:
public partial class MainWindow : Window
{
 private readonly ViewModel _viewModel;
 public MainWindow()
 {
  InitializeComponent();
  _viewModel = new ViewModel();
  DataContext = _viewModel;
 }
}

Legvégül az alap XAML
<Window x:Class="Wpf06_Converter.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <StackPanel Orientation="Horizontal" Height="30">
            <Rectangle  Width="100" Height="30" Fill="Red"/>
        </StackPanel>
    </Grid>
</Window>


Az egyenlőre egyetlen darab piros négyzet kirajzolására szolágló kód hamarosan  kibővül számos új elemmel, amelyek mind-mind a négyzet láthatóságát fogják szabályozni.

Jelen pillanatban van egy Model osztály, amelynek egyetlen bool típusú publikus tulajdonsága van (RectVisibility). Ezt kell valamilyen módon összepárosítani a Rectangle Visibility tulajdonságával.

Ahhoz, hogy mindezt elérjük, egy konverter osztályra lesz szükség. Ez az osztályt egy külön DLL-ben lesz elhelyezve (ez nem szükséges).

Hozzunk létre új Class Library project-et. A neve legyen: Converter.
Töröld ki az automatikusan létrehozott class file-t, és adj a projekt-hez egy új osztályt VisibilityConverter néven.
A Library-hez az alábbi referenciákat kell hozzáadni (jobb klikk a References lenyíló menün, Add refference): PresentationCore, PresentationFrameWork

Az osztály kódja:
namespace Converter
{
 public class VisibilityConverter : IValueConverter
 {
  // ** Model konvertálása az UI felé
  public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
  {
   if ((bool)value)
    return Visibility.Visible;
   else
    return Visibility.Hidden;
  }

  // ** UI konvertálása a Model felé
  public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
  {
   return value;
  }
 }
}

A fenti namespace és osztály elnevezések pontosan egyezzenek! Természetesen a későbbiekben tetszőleges elnevezést lehet használni, de a példa futtatásához ez most nélkülözhetetlen.

Mivel az osztály másik szerelvényben található ezért az osztály kötelezően publikus, hogy a későbbiekben is elérhető maradjon.
Az osztály használatához szükség van az IValueConverter interface implementálására. Ehhez szükség van a System.Windows.Data namespace-re.
using System.Windows.Data;

Az interface két metódus meglétét követeli meg. A tényleges konvertálás ezeken keresztül történik.
public object Convert
public object ConvertBack

A Convert metódus konvertál minden a Model osztályban deklarált tulajdonságot olyan formára, ahogy azt az UI elvárja.
Jelen esetben egy bool tulajdonság a bement, és az alapján dől el a visszatérési érték, amely már feldolgozható a Rectangle Visibility tulajdonsága számára.

A WPF projekthez referenciaként hozzá kell adni az előbb létrehozott Converter szerelvényt.
A hozzáadás után érdemes egy fordítást végezni a kódon, mivel a Wpf számtalanszor nem találja meg elsőre a beimportált elemeket.

Az XAML kód pedig az alábbiak szerint változik.
Egy új xml namespace deklarációval a konvertert deklarálni kell.
xmlns:converter="clr-namespace:Converter;assembly=Converter"
Ezért volt fontos, hogy az namespace és osztály nevek egyezzenek a példában leírtakkal.
Ez a sor megmondja, hogy melyik DLL és melyik namespace kell a kód működéséhez a továbbiakban.

Következő lépésként a Resource-ban definiálni kell a konvertert.
<Grid.Resources>
            <converter:VisibilityConverter x:Key="RectVisibilityConverter"/>
</Grid.Resources>

A converter(az elöbb lett definiálva) a Converter namespace alatt található VisibilityConverter osztályra mutat, az x:Key pedig egy tetszőlegesen elnevezett hivatkozási nevet definiál.

A Rectangle  az alábbiak szerint változik.
<Rectangle  Width="100" Height="30" Fill="Red" Visibility="{Binding MyModel.RectVisibility, Mode=OneWay, Converter={StaticResource RectVisibilityConverter}}"/>

Az adatkapcsolás a Model osztály RectVisibility tulajdonságát használja.

A Mode=OneWay utasítja a keretrendszert, hogy az adatkapcsolás csak egyirányú, azaz csak a Model osztály irányából a Rectangle felé. (az ezt megelőzően egy textboxba írt szöveg tartalmát a framework bemásolta a Model osztály tulajdonságába)

A Converter a Grid.Resource-ban definiált konverterre mutat, ami a bool értéket alakítja át olyan típusra,  amit a Rectangle Visibility tulajdonsága értelmezni tud.

Az XAML jelenleg itt tart:
<Window x:Class="Wpf06_Converter.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:converter="clr-namespace:Converter;assembly=Converter"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Grid.Resources>
            <converter:VisibilityConverter x:Key="RectVisibilityConverter"/>
        </Grid.Resources>
        
        <StackPanel Orientation="Horizontal" Height="30">
            <Rectangle  Width="100" Height="30" Fill="Red" Visibility="{Binding MyModel.RectVisibility, Mode=OneWay, Converter={StaticResource RectVisibilityConverter}}"/>
        </StackPanel>
    </Grid>
</Window>


Lefordítva és lefuttatva a négyzet továbbra is látható.  A ViewModel osztály konstruktorában egyetlen sor található: MyModel = new Model(true);
Amennyiben a true értékét false-ra cseréljük a program futtatásakor a négyzet láthatatlan lesz.

Egészítsük ki a XAML kódot egy CheckBox-al.
<CheckBox  Content="Rect visibility" IsChecked="{Binding MyModel.RectVisibility, Mode=TwoWay}"/>
Mivel az IsChecked bool típust vár ezért nincs szükség konverter használatára.
A Mode=TwoWay (default) miatt a CheckBox változtatásával a Model osztály RectVisibility tulajdonsága is változik.

<Window x:Class="Wpf06_Converter.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:converter="clr-namespace:Converter;assembly=Converter"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Grid.Resources>
            <converter:VisibilityConverter x:Key="RectVisibilityConverter"/>
        </Grid.Resources>
        
        <StackPanel Orientation="Horizontal" Height="30">
            <Rectangle  Width="100" Height="30" Fill="Red" Visibility="{Binding MyModel.RectVisibility, Mode=OneWay, Converter={StaticResource RectVisibilityConverter}}"/>
            <CheckBox Content="Rect visibility" IsChecked="{Binding MyModel.RectVisibility, Mode=TwoWay}"/>
        </StackPanel>
    </Grid>
</Window>


Az eddig megismertek segítségével tetszőleges tulajdonságot lehet bármilyen más típusra alakítani.
A következőkben a User Interface-n történt változtatást fogjuk a Model osztály számára feldolgozni.

A VisibilityConverter osztály ConvertBack metódusát az alábbiak szerint kell megváltoztatni.
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
 var sValue = value as string;
 if (sValue != null)
 {
  if (sValue == "igen")
   return true;

  if (sValue == "nem")
   return false;
 }
 return value;
}

Az XAML-ben pedig egy TextBox-ot kell elhelyezni, amely bemenetként az igen vagy a nem szavakat várja.
<TextBox Text="{Binding MyModel.RectVisibility, Mode=OneWayToSource, Converter={StaticResource RectVisibilityConverter}}" Width="50"/>

A TextBox szintén a Model osztály RectVisibility tulajdonságát használja adatkötésre.

A Mode=OneWayToSource beállítással elérhető, hogy a TextBox tartalmának változásával a Model osztály RectVisibility tulajdonsága megváltozzon, de ha a tulajdonság bárhol máshol megváltozna, azt a TextBox nem követi.
Fontos, hogy ez a VisulStudio szerkesztőjében NullReferenceException-t okozhat, de lefuttatva működni fog. Ha zavaró a hibaüzenet, a beállítás nyugodtan törlésre kerülhet.

A TextBox konvertálásra ugyanazt az osztályt használja mint a Rectangle, csak most visszafelé kell konvertálni.

A végleges XAML kód:
<Window x:Class="Wpf06_Converter.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:converter="clr-namespace:Converter;assembly=Converter"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Grid.Resources>
            <converter:VisibilityConverter x:Key="RectVisibilityConverter"/>
        </Grid.Resources>


        <StackPanel Orientation="Horizontal" Height="30">
            <Rectangle  Width="100" Height="30" Fill="Red" Visibility="{Binding MyModel.RectVisibility, Mode=OneWay, Converter={StaticResource RectVisibilityConverter}}"/>
            <CheckBox Content="Rect visibility" IsChecked="{Binding MyModel.RectVisibility, Mode=TwoWay}"/>

            <TextBlock Margin="20,0,0,0" Text="Látható(ird be igen vagy nem):"/>
            <TextBox Text="{Binding MyModel.RectVisibility, Mode=OneWayToSource, Converter={StaticResource RectVisibilityConverter}}" Width="50"/>
            <Button Content="click"/>
        </StackPanel>
    </Grid>
</Window>


A szövegmező után elhelyezett gomb azért kell, mert a tulajdonság frissítése csak a fókusz elvesztése után történik meg.

Ebben a példában egyetlen tulajdonságot 3 különböző elemhez kapcsoltunk hozzá különböző módon, követve, hogy az adatkötés ténylegesen csak abba az irányba történjen,amelyre feltétlenül szükség van.
A koverter természetesen számos más célra is felhasználható, de a lényege ugyanaz marad. Két tulajdonság között értelmezhető kapcsolat  keletkezzen.

Nincsenek megjegyzések:

Megjegyzés küldése