Uncategorized

First blog post

This is your very first post. Click the Edit link to modify or delete it, or start a new post. If you like, use this post to tell readers why you started this blog and what you plan to do with it.

post

Advertisements
WPF

Material design in XAMLK Toolkit

 

Material design là một trong những thư viện thiết kế giao diện dễ sử dụng trên nhiều nền tảng.

Với Material design chúng ta có thể dễ dàng đưa những ứng dụng máy tính vào cuộc sống, sử dụng ngôn ngữ hiện đại và phổ biến.

Thư viện sử dụng hoàn toàn mã nguồn mở và tương thích với những thư viện phổ biến như MahApps và Dragablz.

Tính năng:

  • Style và điều khiển cho đa số những điều khiển.
  • Nhiều điều khiển bổ sung để hỗ trợ thẩm mĩ và luồng thiết kế material desng.
  • Dễ dàng cấu hình các bảng màu Thiết kế Chất liệu ở cả thiết kế và thời gian chạy.API chuyển tiếp cho các hoạt ảnh GUI dễ dàng xây dựng
  • Hoạt động độc lập, và cũng tương thích với các khuôn khổ WPF phổ biến khác, MahApps và Dragablz
  • MVVM

Hướng dẫn sử dụng

  1. Đầu tiên tạo project sau đó cài đặt MaterialDesignThemes từ
  2. Sửa lại file app.xaml như sau

<?xml version=”1.0″ encoding=”UTF-8″?>
<Application . . .>
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source=”pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.Light.xaml” />
<ResourceDictionary Source=”pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.Defaults.xaml” />
<ResourceDictionary Source=”pack://application:,,,/MaterialDesignColors;component/Themes/Recommended/Primary/MaterialDesignColor.DeepPurple.xaml” />
<ResourceDictionary Source=”pack://application:,,,/MaterialDesignColors;component/Themes/Recommended/Accent/MaterialDesignColor.Lime.xaml” />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>

 

Sau đó sửa lại MainWindow.xaml như sau
Edit MainWindow.xaml to following:

<Window . . .
xmlns:materialDesign=”http://materialdesigninxaml.net/winfx/xaml/themes&#8221;
TextElement.Foreground=”{DynamicResource MaterialDesignBody}”
TextElement.FontWeight=”Regular”
TextElement.FontSize=”13″
TextOptions.TextFormattingMode=”Ideal”
TextOptions.TextRenderingMode=”Auto”
Background=”{DynamicResource MaterialDesignPaper}”
FontFamily=”{DynamicResource MaterialDesignFont}”>
<Grid>
<materialDesign:Card Padding=”32″ Margin=”16″>
<TextBlock Style=”{DynamicResource MaterialDesignTitleTextBlock}”>My First Material Design App</TextBlock>
</materialDesign:Card>
</Grid>
</Window>

 

 

 

Uncategorized

Visual C# 6.0 new features

1.Expression Bodied Methods

Với các phương thức chỉ với 1 dòng code với C# 6.0 bạn đơn giản chỉ cần tạo 1 Expression Bodied member với chỉ 1 biểu thức không sử dụng dấu ngoặc và trả về tường minh

</pre>
class Program

    {

        static void Main(string[] args)

        {

            var t = CongNhan.LuongTheoThang(50);

            Console.WriteLine(t);

            Console.ReadLine();

        }

    }

    class CongNhan

    {

        // Phương thức với chỉ 1 biểu thức

        public static int LuongTheoThang(int luongTheoNgay)

                => luongTheoNgay*30;

    }

<span 				data-mce-type="bookmark" 				id="mce_SELREST_start" 				data-mce-style="overflow:hidden;line-height:0" 				style="overflow:hidden;line-height:0" 			></span>
<pre>

2. Kiểm tra điều kiện Null

Ở các phiên bản trước chúng ta phải kiểm tra điều kiện Null một cách tường minh, với C# 6.0


class Program
{
static void Main(string[] args)
{
CongNhan2 cn2 = new CongNhan2 { Ten="Hoang" };
LayTenCN2(cn2);
Console.ReadLine();
}

// kiểm tra trạng thái null tường minh
public static void LayTenCN1(CongNhan2 cn)
{
string tenCN = "NA";
if (cn!=null) tenCN=cn.Ten;
Console.WriteLine(tenCN);
}

public static void LayTenCN2(CongNhan2 cn)
{
string tenCn = cn?.Ten??"NA";
Console.WriteLine(tenCn);
}
}
class CongNhan2
{
public string Ten { get; set; }
}

3. Tự động khởi tạo thuộc tính

Tự động khởi tạo thuộc tính cho phép lập trình viên khởi tạo thuộc tính mà không sử dụng private set hoặc cần thiết cho biến cục bộ


class CongNhan3
{
public List<string> ChuyenMon { get; } = new List<string> { "Xây dựng", "Thiết kế" };
}

 

Uncategorized

Example 1 MVVM

Ví dụ về sử dụng MVVM trong wpf để xứ lí listview

  1. Model

Model là class Book.cs


[NotifyPropertyChanged]
public class Book
{
private string ten;
private string tacGia;
private string namXuatBan;
private string gia;
public string Ten
{
get
{
return ten;
}

set
{
ten = value;
}
}

public string TacGia
{
get
{
return tacGia;
}

set
{
tacGia = value;
}
}

public string NamXuatBan
{
get
{
return namXuatBan;
}

set
{
namXuatBan = value;
}
}

public string Gia
{
get
{
return gia;
}

set
{
gia = value;
}
}
}

Viewmodel là class MainWindowViewModels.cs Viewmodel sẽ là datacontext của View MainWindow.XAML


[NotifyPropertyChanged]
public class MainWindowViewModels
{
public ObservableCollection LstBooks { set; get; }
public Book CurrBook { get; set; }
public MainWindowViewModels()
{
ObservableCollection lstBooks = new ObservableCollection();
lstBooks.Add(new Book { Ten = "no 1", TacGia = "xuan tra", NamXuatBan = "2015", Gia = "201" });
lstBooks.Add(new Book { Ten = "no 2", TacGia = "xuan tra2", NamXuatBan = "2016", Gia ="121" });
lstBooks.Add(new Book { Ten = "no 3", TacGia = "xuan tra2", NamXuatBan = "2017", Gia = "4214" });
LstBooks = lstBooks;
}
private string ten;
public string Ten
{
get { return CurrBook.Ten; }
set { ten = value; }
}
private string tacGia;
public string TacGia
{
get { return CurrBook.TacGia; }
set { tacGia = value; }
}
private string namXB;
public string NamXB
{
get { return CurrBook.NamXuatBan; }
set { namXB = value; }
}
private string gia;
public string Gia
{
get { return CurrBook.Gia; }
set {
gia = value;
}
}
}

View là file MainWindow.xaml

mainwindow

Capture.PNG

Uncategorized

Delegate trong C#

Trong lúc đợi Visual Studio update một số tính năng mới, mình viết 1 số kiến thức về delagate trong C# cho các bạn còn rối trong vấn đề này.

Một delegate giống như một “người đại diện” hay “đại sứ”. Một delegate có thể được dùng để tạo một bao đóng (encapsulation) cho bất kì phương thức nào, miễn là nó phù hợp (kiểu trả về, tham số). Là một “đại sứ”, delegate có thể triệu gọi phương thức bất kì nơi nào: từ đối tượng này đến đối tượng kia, từ thread này sang thread kia,… Đây là đặc điểm chính của delegate, bạn sẽ cần nhớ lại điều này khi giải quyết các vấn đề thường gặp như truyền dữ liệu giữa hai Form, xử lý lỗi “Cross-thread operation not valid”, tạo event, …

Ngoài ra, bởi vì là một đối tượng, delegate có thể được truyền vào làm tham số của các phương thức. Bạn có thể hiểu đơn giản: delegate là một đối tượng dùng để bao đóng một hoặc nhiều phương thức (Multicast), hay có thể coi delegate là một sự kết hợp giữa đối tượng và phương thức.

Ta sẽ học cách sử dụng delegate qua 3 bước:

  •  Khai báo
  •  Khởi tạo
  • Thực thi

    Khai báo

    Là một đại diện của phương thức và là một kiểu dữ liệu, cách khai báo delegate giống như một sự kết hợp giữa khai báo đối tượng và phương thức. Một delegate được khai báo theo cú pháp sau:

    [modifier] delegate return-type Identifier ([formal-parameters])

    Trong đó:

    • modifier: phạm vi truy xuất (public, private, protected, internal)
    • delegate: từ khóa
    • return-type: kiểu dữ liệu trả về
    • Identifier: định danh của delegate
    • formal-parameters: danh sách các tham số hình thức

    Các phần trong cặp ngoặc vuông [] là tùy chọn.

    Ví dụ:

    public delegate int DoSomething(int x, int y)

    Khai báo một delegate có tên DoSomething, bạn có thể dùng delegate này đại diện cho bất kì phương thức nào yêu cầu hai tham số kiểu int và trả về kiểu int.

    Khởi tạo

    Giả sử  ta có một phương thức sau, có kiểu trả về và tham số tương ứng với delegate DoSomething trên:

    int Add(int x,int y)
    {
    return x+y;
    }

    Khởi tạo delegate từ một phương thức có sẵn, bạn cần truyền phương thức mà delegate sẽ đại diện vào trong constructor. Bạn có thể khởi tạo delegate theo một trong hai cách sau. Chúng tương đương nhau khi được biên dịch:

    // cách 1
    DoSomething myDelegate = new DoSomething(Add)

    // cách 2
    DoSomething myDelegate = Add;

    Hoặc sử dụng tên đầy đủ của phương thức:

    DoSomething obj = MyNamespace1.Program.Add;

    Thay vì tạo phương thức Add() trên, bạn có thể sử dụng anonymous method hoặc lambda expression để tạo đối tượng:

    // anonymous method
    DoSomething obj = delegate(int x,int y){
    return x+y;
    };

    // lambda expression
    DoSomething obj = (x,y) => x+y;

    Thực thi

    Có hai cách để bạn thực thi delegate.

    Coi delegate như một phương thức:

    myDelegate(a, b)

    và như một đối tượng, bằng cách gọi phương thức Invoke():

    myDelegate.Invoke(a, b)

    Ví dụ

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    class Program
    {
        public delegate int DoSomething(int x,int y);
        public static void Main(string[] args)
        {
            Math m=new Math();
            DoSomething obj = (x,y)=>x+y;
            int value=obj(3,4);
            Console.WriteLine(value); // output: 7
            Console.ReadKey();
        }
    }
Uncategorized

Serialize và Deserialize một đối tượng vào file và ngược lại

Lớp BinaryFormatter và SoapFormatter có thể được sử dụng để tuần tự hóa một đối tượng của bất kỳ kiểu nào được gắn với đặc tính System.SerializableAttribute.BinaryFormatter sinh ra một stream dữ liệu nhị phân mô tả đối tượng và trạng thái của nó, trong khi
SoapFormatter sinh ra một tài liệu SOAP.
Cả hai lớp BinaryFormatter và SoapFormatter đều hiện thực giao diện System.Runtime.
Serialization.IFormatter, giao diện này định nghĩa hai phương thức: Serialize và
Deserialize.

  • Serialize – nhận một tham chiếu System.Object và một tham chiếu System.IO.Stream
    làm đối số, tuần tự hóa Object và ghi nó vào Stream.
  • Deserialize nhận một tham chiếu Stream làm đối số, đọc dữ liệu của đối tượng đượctuần-tự-hóa từ Stream, và trả về một tham chiếu Object đến đối tượng được-giải-tuầntự-hóa. Bạn phải ép tham chiếu Object này về kiểu thích hợp.

using System.Collections;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;

namespace ConsoleApplication2
{
class Program
{
static void Main(string[] args)
{
ArrayList people = new ArrayList();
people.Add(“Phuong”);
people.Add(“Phong”);
people.Add(“Nam”);
// Tuần tự hóa đối tượng ArrayList.
FileStream str = File.Create(“people.bin”);
BinaryFormatter bf = new BinaryFormatter();
bf.Serialize(str, people);
str.Close();
// Giải tuần tự hóa đối tượng ArrayList.
str = File.OpenRead(“people.bin”);
bf = new BinaryFormatter();
people = (ArrayList)bf.Deserialize(str);
str.Close();
// Hiển thị nội dung của đối tượng ArrayList
// đã-được-giải-tuần-tự-hóa.
foreach (string s in people)
{
System.Console.WriteLine(s);
}
}
}
}

 

Uncategorized

Kiểu con trỏ trong C#

Trong C# cũng hỗ trợ kiểu con trỏ như trong c++ trong biến con trỏ trong c# phải đặt trong unsafe code và khi debug project phải chọn cho phép debug unsafe code
ví dụ:

int x = 100;
int y =200;

int* p1 = &x; //p1 trỏ vào địa chỉ của x
int* p2 = &y; // p2 trỏ vào địa chỉ thanh ghi của y
Console.WriteLine((int)p1); // in địa chỉ của x;
Console.WriteLine(*p1); in giá trị của địa chỉ x;

WPF

MVVM 1 INotifyPropertyChanged và INotifyCollectionChanged

WPF - Binding Notification ExampleKhi sử dụng data binding, các control hiển thị dữ liệu sẽ động được cập nhật mỗi khi dữ liệu bị thay đổi. Để làm được điều này, các đối tượng dữ liệu được hiện thực interface INotifyPropertyChanged và INotifyCollectionChanged.

 

INotifyPropertyChanged

Namespace: System.ComponentModel

INotifyPropertyChanged chỉ có duy nhất một thành viên là event mang tên PropertyChanged. Khi định nghĩa một class để dùng cho binding, bạn cần kích hoạt event này trong setter cho mỗi property trong class đó.

Ví dụ:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Person:INotifyPropertyChanged
{
    private string _name;
    public string Name
    {
        get { return _name; }
        set
        {
            if (_name != value)
            {
                _name = value;
                OnPropertyChanged("Name");
            }
        }
    }
    protected void OnPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
    public event PropertyChangedEventHandler PropertyChanged;
}

Trong trường hợp dự án của bạn có nhiều class cần hiện thực interface này, tốt nhất là nên tạo một lớp abstract để khỏi phải định nghĩa lại event PropertyChanged trong mỗi class.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
abstract class PropertyChangedBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    protected void OnPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}
class Person : PropertyChangedBase
{
    private string _name;
    public string Name
    {
        get { return _name; }
        set
        {
            if (_name != value)
            {
                _name = value;
                // comment the following line to see the difference when you edit an item
                OnPropertyChanged("Name");
            }
        }
    }
}

Tuy nhiên cách trên vẫn chưa hiệu quả và vẫn còn khá dài dòng. Vậy làm cách nào để có thể khai báo các property một cách đơn giản hơn. Sau một thời gian nghiên cứu một số phương pháp thực hiện, tôi nhận thấy cách đơn giản nhất là sử dụng kĩ thuật reflection và lập trình dynamic của .NET 4. Nếu quan tâm, bạn có thể đọc bài giới thiệu về phương pháp này tại đây:

WPF – Tự động hiện thực INotifyPropertyChanged với DynamicObject.

INotifyCollectionChanged và ObservableCollection(Of T)

Namespace:

System.Collections.Specialized

System.Collections.ObjectModel

Tương tự như interface trên, bạn có thể đoán được công dụng của INotifyCollectionChanged

là cung cấp chức năng thông báo để cập nhật lại giao diện mỗi khi một collection bị thay đổi, thông qua event NotifyCollectionChangedEventHandler CollectionChanged. Các thay đổi này có thể là thêm, xóa, di chuyển, thay thế các phần tử của collection. Trong trường hợp thay đổi các property của một phần tử trong collection, thì phần tử đó phải được hiện thực INotifyPropertyChange thì giao diện mới được cập nhật.

Tham số NotifyCollectionChangedEventArgs của event này xác định kiểu tác động đến collection thông qua enum NotifyCollectionChangedAction. Với mỗi kiểu tác động cần phải sử dụng một constructor tương ứng để tạo NotifyCollectionChangedEventArgs. Bạn sẽ thấy rõ hơn điều này khi tôi làm ví dụ trong phần sau.

Enum NotifyCollectionChangedAction bao gồm các giá trị sau:

Member name

Description

Add One or more items were added to the collection.
Remove One or more items were removed from the collection.
Replace One or more items were replaced in the collection.
Move One or more items were moved within the collection.
Reset The content of the collection changed dramatically.

.NET cung cấp class ObservableCollection(Of T) hiện thực sẵn interface này. Vì thế khi sử dụng data binding trong WPF, bạn hãy sử dụng kiểu collection này thay vì các kiểu truyền thống như ArrayList, List(Of T),…

Trong trường hợp cần chuyển đổi một collection sang kiểu ObservableCollection(Of T), hãy khởi tạo một đối tượng ObservableCollection(Of T) và truyền collection cần chuyển đổi vào constructor của nó.

Tạo một Notifiable Collection

Để hiểu thêm về cách hoạt động của ObservableCollection(Of T), cách tốt nhất là tạo một collection có chức năng tương tự. Tôi sẽ tạo một generic collection thừa kế từ class System.Collections.ObjectModel.Collection(Of T), dĩ nhiên cũng cần phải hiện thực INotifyCollectionChanged.

Class Collection(Of T) chứa sẵn các phương thức public cần thiết để thao tác với các phần tử. Các phương thức public hay indexer của nó đều được hoạt động bằng cách gọi các phương thức protected. Do đó, bạn chỉ cần override các phương thức protected để thêm việc kích hoạt event CollectionChanged.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
class NotifiableCollection : Collection, INotifyCollectionChanged
{
    public NotifiableCollection() { }
    protected override void InsertItem(int index, T item)
    {
        base.InsertItem(index, item);
        OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item));
    }
    protected override void RemoveItem(int index)
    {
        T item = base[index];
        base.RemoveItem(index);
        OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item));
    }
    protected override void ClearItems()
    {
        base.ClearItems();
        OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
    }
    public void Move(int oldIndex, int newIndex)
    {
        T item = base[oldIndex];
        base.SetItem(newIndex, item);
        OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Move,
            item, newIndex, oldIndex));
    }
    protected override void SetItem(int index, T item)
    {
        T oldValue = base[index];
        base.SetItem(index, item);
        OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace,
                item, oldValue));
    }
    #region INotifyCollectionChanged Members
    protected void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
    {
        if (CollectionChanged != null)
            CollectionChanged(this, e);
    }
    public event NotifyCollectionChangedEventHandler CollectionChanged;
    #endregion
}

Ví dụ minh họa

MainWindow.xaml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<Window x:Class="DataChangedNotificationDemo.MainWindow"
        Title="Binding Notification" Height="320" Width="300">
    <StackPanel Margin="5">
        <TextBox Height="24"
                 Name="textBox1" Text="[Your name]" Width="180" />
        <Grid HorizontalAlignment="Center">
            <Grid.RowDefinitions>
                <RowDefinition/>
                <RowDefinition/>
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition/>
                <ColumnDefinition/>
            </Grid.ColumnDefinitions>
            <Button Content="Add" Height="30" Width="80" Margin="5"
                Name="addButton" Click="addButton_Click" />
            <Button Content="Update" Height="30" Width="80" Margin="5"
                    Grid.Column="1"
                Name="updateButton" Click="updateButton_Click" />
            <Button Content="Replace" Height="30" Width="80" Margin="5"
                    Grid.Row="1"
                Name="replaceButton" Click="replaceButton_Click" />
            <Button Content="Clear" Height="30" Width="80" Margin="5"
                    Grid.Column="1" Grid.Row="1"
                Name="clearButton" Click="clearButton_Click" />
        </Grid>
        <ListBox Name="listBox1" ItemsSource="{Binding}"
                 Height="150" Width="200" Margin="5">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <DockPanel>
                        <Button Content="X" Width="20" Height="20"
                                Click="deleteButton_Click"
                                Style="{StaticResource {x:Static ToolBar.ButtonStyleKey}}"/>
                        <TextBlock Text="{Binding Name}" Margin="5,3,0,0"/>
                    </DockPanel>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
    </StackPanel>
</Window>

MainWindow.xaml.cs:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
public partial class MainWindow : Window
{
    NotifiableCollection _persons;
    public MainWindow()
    {
        InitializeComponent();
        _persons = new NotifiableCollection
        {
            new Person{Name="Eros"},
            new Person{Name="Tethys"},
            new Person{Name="Atlas"},
            new Person{Name="Apollo"},
            new Person{Name="Hades"},
        };
        this.DataContext = _persons;
    }
    private void deleteButton_Click(object sender, RoutedEventArgs e)
    {
        var p = (sender as Button).DataContext as Person;
        _persons.Remove(p);
    }
    private void addButton_Click(object sender, RoutedEventArgs e)
    {
        _persons.Add(new Person { Name = textBox1.Text });
    }
    private void updateButton_Click(object sender, RoutedEventArgs e)
    {
        if (listBox1.SelectedItem != null)
            _persons[listBox1.SelectedIndex].Name  = textBox1.Text;
    }
    private void clearButton_Click(object sender, RoutedEventArgs e)
    {
        _persons.Clear();
    }
    private void replaceButton_Click(object sender, RoutedEventArgs e)
    {
        if (listBox1.SelectedItem != null)
        {
            _persons[listBox1.SelectedIndex] = new Person { Name = textBox1.Text };
            listBox1.Items.Refresh();
        }
    }
}

Giao diện:

WPF - Binding Notification Example

Tổng kết

Ví dụ đơn giản trên là cách thông thường để viết ứng dụng và nó không theo mô hình nào cả. Sự trộn lẫn giữa các tầng giao diện, xử lý, dữ liệu khiến cho ứng dụng thiếu linh hoạt và khó phát triển.

Trong phần tiếp theo, bạn sẽ thấy cách tôi sử dụng Command để thay thế phương thức xử lý sự kiện. Việc ứng dụng Command giúp cho các phần của ứng dụng được tách biệt rõ ràng hơn. Lý do là các Command được định nghĩa trong phần ViewModel, và theo nguyên tắc chúng không thể truy xuất được các thành phần của View (giao diện).

https://yinyangit.wordpress.com

Bài liên quan

Cơ bản về MVVM (Model – View – ViewModel) Pattern

https://yinyangit.wordpress.com