Showing posts with label Inheritance. Show all posts
Showing posts with label Inheritance. Show all posts

Wednesday, January 30, 2013

Creating an Attribute in C#

In C#, attribute is one of the common declarative technique that most of us is using into our application. Usually, we use the attribute to tag or flag a class, method or property in the form of object base recognition. It also simplify our determination in every object.

Furthermore, in C# there are lots of pre-defined attribute that we can embed into our application. One common sample is SerializableAttribute which flag/tag our class to participate in serialization/deserialization process. If however you would like to extend the attribute functionality and define your own, you can extend/inherit the attribute class from System namespace.

Please visit C# Flags Attribute blog before you continue. The reason is to let you explore the enumeration named Privilege.

Now, we are expecting that you already explore the Privilege enumeration form mentioned  blog above.

Let say for example you would like to define a scenario that gives your code-level to determine whether a class (or a stub object) can be deleted from database. See below our sample custom attribute.

public class SecurityAccess : Attribute
{
    public SecurityAccess()
    {
    }

    public SecurityAccess(Privilege privilege)
        : this()
    {
        this.Privilege = privilege;
    }

    public Privilege Privilege
    {
        get;
        set;
    }
}

What the class SecurityAccess is doing above is just simply handling the value for the Privilege enumeration. This property defines whether the class that declare this attribute has an enough level of privilege before doing necessary action in the database.

How to use custom attribute?

Now, in this section, we will going to guide you how to use the attribute we created above.

Suppose we have two class that inherits from one object. Let's call them Animal, and the two other class in Cat and Dog. See below implementation.

public class Animal
{
    public bool IsDeletable
    {
        get;
        protected set;
    }
}

public class Cat : Animal
{
    public Cat()
    {
    }
}

public class Dog : Animal
{
    public Dog()
    {
    }
}

The Dog and Cat is an Animal base on our implementation. With the use of our custom attribute named SecurityPrivilege, we can declare each privilege in every class. Suppose we would like different breed of Cat cannot be deleted from our database, then we can declare our custom attribute like below.

[SecurityAccess(Privilege = Privilege.Read | Privilege.Write | Privilege.Create)]
public class Cat : Animal
{
    public Cat()
    {

    }
}

And the Dog breed is deletable. See below.

[SecurityAccess(Privilege = Privilege.Read | Privilege.Write | Privilege.Create | Privilege.Delete)]
public class Dog : Animal
{
    public Dog()
    {

    }
}

How to access an attribute value declared in the class/method and other object?

Now, in this section, we will going to guide you how to access the value of the attribute per class level.

In C#, the only way to get the attribute value is to use Reflection. First, we need to get the current type of the object, from there check if there are declared custom attributes and determine the attribute type. If its match the attribute we're looking for, then that is the custom attribute we had created. See below the process.

protected T GetCustomAttribute<T>(object @object)
{
    if (!object.ReferenceEquals(@object, null))
    {
        var type = @object.GetType();
        var attrs = type.GetCustomAttributes(false);
        if (attrs.Length > 0)
        {
            foreach (var attr in attrs)
            {
                if (attr is T)
                {
                    return (T)(object)attr;
                }
            }
        }
    }
    return default(T);
}

What the code is doing above is to simply return the embedded attribute in the object you passed in the parameter named @object. If there is no attribute found, then it will return null. Best to place this code in the Animal base class so both derive class can use it.

And now, in the construction of Dog and Cat class, you should call the method directly from there.

public Dog()
{
    var attr = this.GetCustomAttribute<SecurityAccess>(this);
    base.IsDeletable = (attr.Privilege & Privilege.Delete) == Privilege.Delete;
}

public Cat()
{
    var attr = this.GetCustomAttribute<SecurityAccess>(this);
    base.IsDeletable = (attr.Privilege & Privilege.Delete) == Privilege.Delete;
}

Now, in the base class IsDeletable property, we then set it depends on the privilege level we declared on the class.

Please note that we can override the attribute value declared in the base class into derive class. So if you want that the Siamese Cat breed be deletable, you can declare your own SecurityAccess attribute in Siamese class.

That's all about this blog. Please follow us so you will get more interesting topics soon.

Please visit Microsoft documentation for further details.

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.