Showing posts with label XAML. Show all posts
Showing posts with label XAML. Show all posts

Sunday, January 27, 2013

WPF Binding to a Static Instance

This tutorial will guide you how to bind in a static property of a class in a two directional way in WPF.

Two way Binding in a Static Property

In order for our class StaticBinder to participates in the two directional binding, we should implement the IPropertyNotifyChanged event. In our case, we are requiring you to read our pre-requisite blog so you will fully understand the implementation.

List of pre-requisite blogs.

And here we are expecting that you're finish reading the pre-requisite blogs above.

Now let's start with our explanation.

We need to modify our existing implementation of StaticBinder class and force inherit from the BaseObject class. With this, the StaticBinder will also inherit the implementation of the super class PropertyNotifier that implements the IPropertyNotifier interface.

In each property of the StaticBinder class, we should use the BaseObject GetValue/SetValue accessor so it will notify the listener on every property value changed. Please note that you need not to implement each method in a static way. In our case, you must remove the static keyword in each property.

Now, last thing to do within StaticBinder class is to declare an additional static property named Instance. This approach is a single-ton approach for the StaticBinder class. This new property named Instance will participate in WPF binding. See below our new class implementation.

public class StaticBinder : BaseObject
{
    private static StaticBinder __instance = null;

    static StaticBinder()
    {
        __instance = new StaticBinder();
    }

    public StaticBinder()
    {
        MSG_Cancel = "Welcome";
        MSG_OK = "OK";
        MSG_Welcome = string.Format("Welcome {0}!", "WPF Binding");
    }

    public string MSG_Cancel
    {
        get { return base.GetValue<string>("MSG_Cancel"); }
        set { base.SetValue("MSG_Cancel", value); }
    }

    public string MSG_OK
    {
        get { return base.GetValue<string>("MSG_OK"); }
        set { base.SetValue("MSG_OK", value); }
    }

    public string MSG_Welcome
    {
        get { return base.GetValue<string>("MSG_Welcome"); }
        set { base.SetValue("MSG_Welcome", value); }
    }

    public static StaticBinder Instance
    {
        get { return __instance; }
    }
}

Binding in XAML

Now, if we are binding a static property in XAML, we need to use the namespace referencing and direct call the static property of that instance. Additionally, you can bind directly to the property of that shared instance which is in our case it is the StaticBinder class.

With the help of the Binding (x:Static) keyword, the trick will be addressed. See below our codes in the XAML in yellow background.

<Window x:Class="CodesDirectory.WIN_StaticBinding"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="WIN Static Binding" Height="300" Width="300"
        xmlns:classes="clr-namespace:CodesDirectory.Classes"
        WindowStartupLocation="CenterScreen" ResizeMode="NoResize">
    <Grid Margin="5">
        <Grid.RowDefinitions>
            <RowDefinition Height="*"></RowDefinition>
            <RowDefinition Height="Auto"></RowDefinition>
        </Grid.RowDefinitions>
        <Label Grid.Row="0"
               Content="{Binding Source={x:Static classes:StaticBinder.Instance}, Path=MSG_Welcome}"></Label>
        <StackPanel Grid.Row="1" HorizontalAlignment="Right" Orientation="Horizontal">
            <Button Name="okayButton"
                    Click="okayButton_Click" Content="{Binding Source={x:Static classes:StaticBinder.Instance}, Path=MSG_OK}"
                    Width="100"
                    Margin="5"></Button>
            <Button Name="cancelButton"
                    Content="{Binding Source={x:Static classes:StaticBinder.Instance}, Path=MSG_Cancel}"
                    Width="100"
                    Margin="5"></Button>
        </StackPanel>
    </Grid>
</Window>

For you to check the actual two-dimensional binding, please follow the code behind implementation below.

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

    private void okayButton_Click(object sender, RoutedEventArgs e)
    {
        Classes.StaticBinder.Instance.MSG_OK = "Okay";
    }
}

You will notice that upon click of the OK button, the changes will then reflect to the UI.

And that's all about it. You just finished reading this blog.

WPF Binding to a Static Property

In WPF, the binding provides an efficient and easy way of data interaction between components. Every FrameworkElement can be bound to any data sources in the form of XAML and CLR.

The binding technique is one of the best way to simplify the implementations in WPF. But you should understand that every class in .NET only participates in two way binding if you have implemented the IPropertyChanged interface or you used DependencyProperty object.

Binding to a Static Property

Binding to static property of the class is slightly complex than normal binding. If you have a class with static property, you atleast need to address the requirement whether the binding is one or two dimensional. Please note that static property is not an instance type property and you should do your own trick to notify the changes in the XAML.

First of all, we should create a class (note: don't make it static). Then, place all static property you wish to put in this class. In our case, we will name the class StaticBinder and will put three property named MSG_OK, MSG_Cancel and MSG_Welcome. See below the implementation.

public class StaticBinder
{
    static StaticBinder()
    {
        MSG_Cancel = "Welcome";
        MSG_OK = "OK";
        MSG_Welcome = string.Format("Welcome {0}!", "WPF Binding");
    }

    public static string MSG_Cancel
    {
        get;
        set;
    }

    public static string MSG_OK
    {
        get;
        set;
    }

    public static string MSG_Welcome
    {
        get;
        set;
    }
}

You will notice that we created one static constructor. In there, we place all the property initialization and call each of them statically. Now, in the XAML, we need to create a Window with Label and Button controls embedded that can be bound on the class above. See below our implementation.

<Window x:Class="CodesDirectory.WIN_StaticBinding"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="WIN Static Binding" Height="300" Width="300"
        WindowStartupLocation="CenterScreen" ResizeMode="NoResize">
    <Grid Margin="5">
        <Grid.RowDefinitions>
            <RowDefinition Height="*"></RowDefinition>
            <RowDefinition Height="Auto"></RowDefinition>
        </Grid.RowDefinitions>
        <Label Grid.Row="0"></Label>
        <StackPanel Grid.Row="1" HorizontalAlignment="Right" Orientation="Horizontal">
            <Button Name="okayButton"
                    Click="okayButton_Click"
                    Width="100"
                    Margin="5"></Button>
            <Button Name="cancelButton"
                    Width="100"
                    Margin="5"></Button>
        </StackPanel>
    </Grid>
</Window>

Now before you can participate your Window in the binding, you should create a reference to the class we created above via XAML. You should put an xmlns (XML Namespace) that directs to the namespace where the StaticBinder class resides, and you should create a new instance of StaticBinder class and put it in the Window resources. See below the additional codes in yellow background.

<Window x:Class="CodesDirectory.WIN_StaticBinding"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="WIN Static Binding" Height="300" Width="300"
        xmlns:classes="clr-namespace:CodesDirectory.Classes"
        WindowStartupLocation="CenterScreen" ResizeMode="NoResize">
    <Window.Resources>
        <ObjectDataProvider x:Key="staticBinder"
                            ObjectType="{x:Type classes:StaticBinder}"></ObjectDataProvider>
    </Window.Resources>
    <Grid Margin="5">
        <Grid.RowDefinitions>
            <RowDefinition Height="*"></RowDefinition>
            <RowDefinition Height="Auto"></RowDefinition>
        </Grid.RowDefinitions>
        <Label Grid.Row="0"></Label>
        <StackPanel Grid.Row="1" HorizontalAlignment="Right" Orientation="Horizontal">
            <Button Name="okayButton"
                    Click="okayButton_Click"
                    Width="100"
                    Margin="5"></Button>
            <Button Name="cancelButton"
                    Width="100"
                    Margin="5"></Button>
        </StackPanel>
    </Grid>
</Window>

With the XAML codes above, you will notice that our namespace name is 'classes'. You can change it in any name if you wish. Also, we created a new object named ObjectDataProvider with key value staticBinder. The ObjectDataProvider is an object that provides us a dynamic resource data binder in the XAML. It can also be used to instantiate and call the class methods in your application.

Let's continue to the actual binding. Now in the Content property of the Button and Label, use the StaticResource that connects the ObjectDataProvider and set the Path property equal to the actual Property of the StaticBinder class. See below the codes in yellow background.

<Window x:Class="CodesDirectory.WIN_StaticBinding"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="WIN Static Binding" Height="300" Width="300"
        xmlns:classes="clr-namespace:CodesDirectory.Classes"
        WindowStartupLocation="CenterScreen" ResizeMode="NoResize">
    <Window.Resources>
        <ObjectDataProvider x:Key="staticBinder"
                            ObjectType="{x:Type classes:StaticBinder}"></ObjectDataProvider>
    </Window.Resources>
    <Grid Margin="5">
        <Grid.RowDefinitions>
            <RowDefinition Height="*"></RowDefinition>
            <RowDefinition Height="Auto"></RowDefinition>
        </Grid.RowDefinitions>
        <Label Grid.Row="0"
               Content="{Binding Source={StaticResource ResourceKey=staticBinder}, Path=MSG_Welcome}"></Label>
        <StackPanel Grid.Row="1" HorizontalAlignment="Right" Orientation="Horizontal">
            <Button Name="okayButton"
                    Click="okayButton_Click"
                    Content="{Binding Source={StaticResource ResourceKey=staticBinder}, Path=MSG_OK}"
                    Width="100"
                    Margin="5"></Button>
            <Button Name="cancelButton"
                    Content="{Binding Source={StaticResource ResourceKey=staticBinder}, Path=MSG_Cancel}"
                    Width="100"
                    Margin="5"></Button>
        </StackPanel>
    </Grid>
</Window>

Please note that the implementation above doesn't participates in the two directional binding. Any changes happens on the StaticBinder class property will not reflect in the UI. Please continue read the blog WPF Binding to Static Instance if you wish to understand how to implement two directional binding.

Friday, January 25, 2013

PART 2: C# WPF TreeView File Explorer with System Icons

This blog is just a continuation of our previous blog PART 1: C# WPF TreeView File Explorer with System Icons. So please visit it first before continuing in this blog.

And here, we are expecting that you already read the previous blog. Now, you're ready to go in this blog.

Actual Wrapper Implementation

Below you can see the actual implementation of the FileSystemObjectInfo wrapper class that we will going to use in our application.

public class FileSystemObjectInfo : BaseObject
{
    public FileSystemObjectInfo(FileSystemInfo info)
    {
        if (this is DummyFileSystemObjectInfo) return;
        this.Children = new ObservableCollection<FileSystemObjectInfo>();
        this.FileSystemInfo = info;
        if (info is DirectoryInfo)
        {
            this.ImageSource = FolderManager.GetImageSource(info.FullName, ItemState.Close);
            this.AddDummy();
        }
        else if (info is FileInfo)
        {
            this.ImageSource = FileManager.GetImageSource(info.FullName);
        }
        this.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler(FileSystemObjectInfo_PropertyChanged);
    }

    public FileSystemObjectInfo(DriveInfo drive)
        : this(drive.RootDirectory)
    {
        this.Drive = drive;
    }

    #region Properties

    public ObservableCollection<FileSystemObjectInfo> Children
    {
        get { return base.GetValue<ObservableCollection<FileSystemObjectInfo>>("Children"); }
        private set { base.SetValue("Children", value); }
    }

    public ImageSource ImageSource
    {
        get { return base.GetValue<ImageSource>("ImageSource"); }
        private set { base.SetValue("ImageSource", value); }
    }

    public bool IsExpanded
    {
        get { return base.GetValue<bool>("IsExpanded"); }
        set { base.SetValue("IsExpanded", value); }
    }

    public FileSystemInfo FileSystemInfo
    {
        get { return base.GetValue<FileSystemInfo>("FileSystemInfo"); }
        private set { base.SetValue("FileSystemInfo", value); }
    }

    private DriveInfo Drive
    {
        get { return base.GetValue<DriveInfo>("Drive"); }
        set { base.SetValue("Drive", value); }
    }

    #endregion

    #region Methods

    private void AddDummy()
    {
        this.Children.Add(new DummyFileSystemObjectInfo());
    }

    private bool HasDummy()
    {
        return !object.ReferenceEquals(this.GetDummy(), null);
    }

    private DummyFileSystemObjectInfo GetDummy()
    {
        var list = this.Children.OfType<DummyFileSystemObjectInfo>().ToList();
        if (list.Count > 0) return list.First();
        return null;
    }

    private void RemoveDummy()
    {
        this.Children.Remove(this.GetDummy());
    }

    private void ExploreDirectories()
    {
        if (!object.ReferenceEquals(this.Drive, null))
        {
            if (!this.Drive.IsReady) return;
        }
        try
        {
            if (this.FileSystemInfo is DirectoryInfo)
            {
                var directories = ((DirectoryInfo)this.FileSystemInfo).GetDirectories();
                foreach (var directory in directories.OrderBy(d => d.Name))
                {
                    if (!object.Equals((directory.Attributes & FileAttributes.System), FileAttributes.System) &&
                        !object.Equals((directory.Attributes & FileAttributes.Hidden), FileAttributes.Hidden))
                    {
                        this.Children.Add(new FileSystemObjectInfo(directory));
                    }
                }
            }
        }
        catch
        {
            /*throw;*/
        }
    }

    private void ExploreFiles()
    {
        if (!object.ReferenceEquals(this.Drive, null))
        {
            if (!this.Drive.IsReady) return;
        }
        try
        {
            if (this.FileSystemInfo is DirectoryInfo)
            {
                var files = ((DirectoryInfo)this.FileSystemInfo).GetFiles();
                foreach (var file in files.OrderBy(d => d.Name))
                {
                    if (!object.Equals((file.Attributes & FileAttributes.System), FileAttributes.System) &&
                        !object.Equals((file.Attributes & FileAttributes.Hidden), FileAttributes.Hidden))
                    {
                        this.Children.Add(new FileSystemObjectInfo(file));
                    }
                }
            }
        }
        catch
        {
            /*throw;*/
        }
    }

    #endregion

    void FileSystemObjectInfo_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
    {
        if (this.FileSystemInfo is DirectoryInfo)
        {
            if (string.Equals(e.PropertyName, "IsExpanded", StringComparison.CurrentCultureIgnoreCase))
            {
                if (this.IsExpanded)
                {
                    this.ImageSource = Shell.FolderManager.GetImageSource(this.FileSystemInfo.FullName, ItemState.Open);
                    if (this.HasDummy())
                    {
                        this.RemoveDummy();
                        this.ExploreDirectories();
                        this.ExploreFiles();
                    }
                }
                else
                {
                    this.ImageSource = Shell.FolderManager.GetImageSource(this.FileSystemInfo.FullName, ItemState.Close);
                }
            }
        }
    }

    private class DummyFileSystemObjectInfo : FileSystemObjectInfo
    {
        public DummyFileSystemObjectInfo()
            : base(new DirectoryInfo("DummyFileSystemObjectInfo"))
        {
        }
    }
}

Now, let's go the XAML and WPF stuff bindings.

We need to create a new Window object in our solution and add a new TreeView inside it. See below the code.

<Window x:Class="CodesDirectory.WIN_TreeViewWithIcon"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:classes="clr-namespace:CodesDirectory.Classes"
        Title="WIN TreeView with System Icons" Height="300" Width="300">
    <TreeView Name="treeView" Margin="5"></TreeView>
</Window>

We also need to override the default style of the items. In this case the ItemContainerStyle value should be modified. But unlike with other Style we only bind the IsExpanded property of the TreeViewItem into the IsExpanded property of FileSystemObjectInfo class (two-way direction). So every user action in the TreeViewItem state will also be applied in the bound objects. See below our new code.

<Window x:Class="CodesDirectory.WIN_TreeViewWithIcon"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:classes="clr-namespace:CodesDirectory.Classes"
        Title="WIN TreeView with System Icons" Height="300" Width="300">
    <TreeView Name="treeView" Margin="5">
        <TreeView.ItemContainerStyle>
            <Style TargetType="{x:Type TreeViewItem}">
                <Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
                <Setter Property="KeyboardNavigation.AcceptsReturn" Value="True" />
            </Style>
        </TreeView.ItemContainerStyle>
    </TreeView>
</Window>

After that in the Resources property of the TreeView object. We have to use the HierarchicalDataTemplate object and targets the actual FileSystemObjectInfo class in the DataType property. With the use of this object, we can set the actual template that the TreeViewItem has participated. In our case, we need to create a template where there is an Image in the left and Label in the right. The image will do display the actual icon of the file system, it binds the ImageSource property of the FileSystemObjectInfo class, and the Label will be bind in the Name property of the FileSystemInfo (of type System.IO.FileSystemInfo) property of the FileSystemObjectInfo class. See below our actual codes now.

<Window x:Class="CodesDirectory.WIN_TreeViewWithIcon"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:classes="clr-namespace:CodesDirectory.Classes"
        Title="WIN TreeView with System Icons" Height="300" Width="300">
    <TreeView Name="treeView" Margin="5">
        <TreeView.ItemContainerStyle>
            <Style TargetType="{x:Type TreeViewItem}">
                <Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
                <Setter Property="KeyboardNavigation.AcceptsReturn" Value="True" />
            </Style>
        </TreeView.ItemContainerStyle>
        <TreeView.Resources>
            <HierarchicalDataTemplate DataType="{x:Type classes:FileSystemObjectInfo}" ItemsSource="{Binding Path=Children}">
                <StackPanel Orientation="Horizontal">
                    <Image Source="{Binding Path=ImageSource, UpdateSourceTrigger=PropertyChanged}" Margin="0,1,8,1"></Image>
                    <TextBlock Text="{Binding Path=FileSystemInfo.Name}"></TextBlock>
                </StackPanel>
            </HierarchicalDataTemplate>
        </TreeView.Resources>
    </TreeView>
</Window>

Lastly, in the code behind, we need to explore the top level drives and add each drive in the FileSystemObjectInfo wrapper class and append it to the TreeView.Items property. See below the codes on how to do it.

public partial class WIN_TreeViewWithIcon : Window
{
    public WIN_TreeViewWithIcon()
    {
        InitializeComponent();
        var drives = DriveInfo.GetDrives();
        foreach (var drive in drives)
        {
            this.treeView.Items.Add(new FileSystemObjectInfo(drive));
        }
    }
}

And, congratulations to you for finishing this blog. You are now equipped with a new interesting programming technique called Shell.

Please follow us and be part of this blog site for the more very interesting stuff.