2018/06/19

Extending the Shown Event in WPF


Using the Shown Event.

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();

        var eventExtend = this.EventExtend();
        eventExtend.AddShownEventHandler(this.MainWindow_Shown);
    }

    public void MainWindow_Shown(object sender, EventArgs e)
    {
            
    }
}

Fire the Shown Event using the LayoutUpdated Event.


public class FrameworkElementEventExtend
{
    public event EventHandler Shown;

    private bool initialized;
    private readonly FrameworkElement frameworkElement;

    public FrameworkElementEventExtend(FrameworkElement frameworkElement)
    {
        this.frameworkElement = frameworkElement;

        this.frameworkElement.LayoutUpdated += FrameworkElement_LayoutUpdated;
    }

    private void FrameworkElement_LayoutUpdated(object sender, EventArgs e)
    {
        if (!this.initialized && (this.frameworkElement.ActualHeight > 0 || this.frameworkElement.ActualWidth > 0))
        {
            this.Shown?.Invoke(this, EventArgs.Empty);

            this.initialized = true;

            this.frameworkElement.LayoutUpdated -= FrameworkElement_LayoutUpdated;
        }
    }
}

public static class FrameworkElementEventExtendHelper
{
    public static FrameworkElementEventExtend EventExtend(this FrameworkElement frameworkElement)
    {
        FrameworkElementEventExtend eventExtend = new FrameworkElementEventExtend(frameworkElement);
        return eventExtend;
    }

    public static void AddShownEventHandler(this FrameworkElementEventExtend eventExtend, EventHandler eventHandler)
    {
        eventExtend.Shown += eventHandler;
    }
}

2018/04/19

C# IDisposable구현의 기본 패턴

안전하고 일관성있는 관리를 위한 기본 삭제 패턴이다.

관리되지 않는 리소스를 포함하는 형식은 IDisposable을 구현한다.

해당 형식을 상속받는 형식에서의 리소스 관리도 고려 해야 한다.
이를 위하여 가상(virtual)함수를 사용한다.

해당 인스턴스 사용 후에는 Dispose 함수를 호출하여 리소스 삭제를 가능하게 한다.

Dispose 함수 호출의 누락을 대비하여 소멸자를 구현한다.

Dispose 함수 호출로 리소스를 삭제한다면 소멸자 호출이 필요 없으므로 이를 GC에 알린다. 소멸자 호출을 필요로 하는 인스턴스는 GC에의한 수집이 늦어 지기 때문이다.

기본 IDisposable 구현
public class MyUnmanagedClass : IDisposable
{
    IntPtr unmanagedResource;
    bool disposed = false;

    public MyUnmanagedClass()
    {
        //비관리 리소스 할당.
        //unmanagedResource = ...
    }

    public void Dispose()
    {
        this.Dispose(true);
        //소멸자를 호출 하지 않도록 설정.
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (!this.disposed)
        {
            if (disposing)
            {
                // 관리 리소스 해지.
            }

            // 비관리 리소스 해지.
            unmanagedResource = IntPtr.Zero;

            this.disposed = true;                
        }
    }

    ~MyUnmanagedClass()
    {
        this.Dispose(false);
    }
}

하위 형식에서의 리소스 관리
public class MyUnmanagedClass2 : MyUnmanagedClass
{
    bool disposed = false;

    protected override void Dispose(bool disposing)
    {
        if (!this.disposed)
        {
            if (disposing)
            {
                // 관리 리소스 해지.
            }

            // 비관리 리소스 해지.                

            this.disposed = true;
        }

        base.Dispose(disposing);
    }
}

사용예
public static class TestClass
{
    public static void Test()
    {
        var myUnmanagedClass = new MyUnmanagedClass();
        myUnmanagedClass.Dispose();

        using (var myUnmanagedClass2 = new MyUnmanagedClass2())
        { }
    }
}

2018/03/20

WinForm Title Bar 없이 폼이동

MouseDown, MouseMove, MouseUp 이벤트를 이용하여 폼이동을 처리 한다.

MouseDown 시점에 Control.MousePosition 값과 MouseMove 시점 Control.MousePosition 값의 차이 만큼 폼을 이동 한다..

샘플은 폼 또는 Label 을 마우스로 이동하여 폼을 이동 한다.

Sample Form

기능 구현.

public static class FormHelper
{
    public static void SetMovingForm(this Form form, Control[] triggerControls, Cursor movingCursorOrNull = null)
    {
        bool mouseDown = false;
        Point lastMousePoint = new Point();
        Cursor movingCursor;

        if (movingCursorOrNull == null)
            movingCursor = Cursors.NoMove2D;
        else
            movingCursor = movingCursorOrNull;

        Dictionary<Control, Cursor> oldCursors = new Dictionary<Control, Cursor>();

        foreach (var item in triggerControls)
        {
            Control con = item;

            con.MouseDown += (s, e) =>
            {
                var thisControl = s as Control;

                mouseDown = true;

                if (!oldCursors.ContainsKey(item))
                    oldCursors.Add(item, thisControl.Cursor);
                else
                    oldCursors[item] = thisControl.Cursor;

                thisControl.Cursor = movingCursor;

                lastMousePoint = Control.MousePosition;
            };                

            con.MouseMove += (s, e) =>
            {
                if (!mouseDown)
                    return;

                Point currentPoint = Control.MousePosition;

                int x = currentPoint.X - lastMousePoint.X;
                int y = currentPoint.Y - lastMousePoint.Y;

                form.Location = new Point(form.Location.X + x, form.Location.Y + y);

                lastMousePoint = currentPoint;
            };

            con.MouseUp += (s, e) =>
            {
                var thisControl = s as Control;

                mouseDown = false;
                thisControl.Cursor = oldCursors[thisControl];
            };
        }
    }
}

사용예.

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();

        this.FormBorderStyle = FormBorderStyle.None;

        this.SetMovingForm(new Control[] { this, this.titleLabel });
    }
}

2018/01/05

C# 이벤트 발생

이벤트를 발생하는 방법.

속성의 값이 변경되면 이벤트를 발생하고 처리를 해보자.

event 키워드를 사용하여 이벤트를 선언 하고
이벤트 대리자를(핸들러 메서드 형식) 지정 한다.

public event EventHandler ValueChanged;

대리자는 반환값이 없고(void) 이벤트 발생 객체 와 이벤트 데이터를 인자로 한다.

public delegate void EventHandler(object sender, EventArgs e);

이 형식을 무시해도 되지만 MS 가이드를 따르도록 한다.
이벤트 핸들러가 void가 아니면 의미도 이상하다.

다음은 이벤트 선언과 발생의 코드 이다.

class Item
{
    public event EventHandler ValueChanged;

    private int value;
    public int Value
    {
        get => this.value;

        set
        {
            if (this.value != value)
            {
                this.value = value;
                OnValueChanged(EventArgs.Empty);
            }
        }
    }

    protected virtual void OnValueChanged(EventArgs e)
    {
        this.ValueChanged?.Invoke(this, e);
    }
}

속성 값 Value의 값이 변경 되었을때 이벤트를 발생한다.
Null 조건 연산자를 사용하여 스레드로부터 안전한방식으로 대리자를 호출한다.

상속을 고려하지 않는다면 OnValueChanged 메서드를 전용(private) 으로 작성해도 무관하다.

다음은 이벤트처리 코드 이다.

Item item = new Item();

item.ValueChanged += (s, e) => 
{
    Console.WriteLine($"new value: {item.Value}");
};

item.Value = 10;

다음은 조금 수정하여 이벤트 데이터를 사용하여 변경전값과 현재값을 전달한다.

이벤트 데이터 정의:

class ValueChangedEventArgs : EventArgs
{
    public int OldValue { get; }
    public int NewValue { get; }

    public ValueChangedEventArgs(int oldValue, int newValue)
    {
        this.OldValue = oldValue;
        this.NewValue = newValue;
    }        
}

이벤트 데이터 사용:

class Item
{
    public event EventHandler<ValueChangedEventArgs> ValueChanged;

    private int value;
    public int Value
    {
        get => this.value;

        set
        {
            if (this.value != value)
            {
                var oldValue = this.value;
                this.value = value;
                OnValueChanged(new ValueChangedEventArgs(oldValue, this.value));
            }
        }
    }

    protected virtual void OnValueChanged(ValueChangedEventArgs e)
    {
        this.ValueChanged?.Invoke(this, e);
    }
}

이벤트 핸들러에서 데이터 사용:

Item item = new Item();

item.ValueChanged += (s, e) =>
{
    Console.WriteLine($"old value: {e.OldValue}, new value: {e.NewValue}");
};

item.Value = 10;

이번에는 여러 속성에서 사용 할 수 있도록 변경 한다.
속성명을 알아내기 위해 CallerMemberName Attribute 를 사용 했다.

수정된 전체 코드:

class PropertyValueChangedEventArgs : EventArgs
{
    public string PropertyName { get; }
    public object OldValue { get; }
    public object NewValue { get; }

    public PropertyValueChangedEventArgs(string propertyName, object oldValue, object newValue)
    {
        this.PropertyName = propertyName;
        this.OldValue = oldValue;
        this.NewValue = newValue;
    }
}

class Item
{
    public event EventHandler<PropertyValueChangedEventArgs> PropertyValueChanged;

    private int intValue;
    public int IntValue
    {
        get => this.intValue;

        set
        {
            if (this.intValue != value)
            {
                var oldValue = this.intValue;
                this.intValue = value;
                OnPropertyValueChanged(oldValue, this.intValue);
            }
        }
    }

    private string strValue;
    public string StrValue
    {
        get => this.strValue;

        set
        {
            if (this.strValue != value)
            {
                var oldValue = this.strValue;
                this.strValue = value;
                OnPropertyValueChanged(oldValue, this.strValue);
            }
        }
    }

    private void OnPropertyValueChanged(            
        object oldValue, object newValue,
        [CallerMemberName] string memberName = "")
    {
        this.PropertyValueChanged?.Invoke(this, new PropertyValueChangedEventArgs(memberName, oldValue, newValue));
    }
}

class Program
{
    static void Main(string[] args)
    {
        Item item = new Item();

        item.PropertyValueChanged += (s, e) =>
        {
            Console.WriteLine($"property name: {e.PropertyName} old value: {e.OldValue}, new value: {e.NewValue}");
        };

        item.IntValue = 10;
    }
}

2017/12/21

C# 6.0 Null 조건 연산자


하위 데이터 사용 시 null 검사를 하는 기존 방법:

var id = parent == null ? null : parent.ID;

C# 6.0 에 추가된 Null 조건 연산자를 사용하면 유용하다.

var id = parent?.ID;

var id = parent?.Parent?.ID;

인덱서에 Null 조건 연산자 사용:

var id = parents?[0]?.ID;

또한 스레드로부터 안전한 방식으로 대리자를 호출하는 데 사용된다.

기존 방식:

var handler = this.ValueChanged;
if(handler != null)
   handler(this, e);

Null 조건 연산자 사용:

this.ValueChanged?.Invoke(this, e);

2017/11/28

C# yield return, 확장 메서드를 이용하여 Enumerable.Where 만들기

Enumerable.Where 와 같은 동작을 하는 Where2 를 만들어 보자.

public static class EnumerableEx
{
    public static IEnumerable<TSource> Where2<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
    {
        foreach (var item in source)
        {
            if (predicate(item))
                yield return item;
        }
    }
}

확장 메서드로 만들어서 IEnumerable.Where2 로 사용 가능하며
yield return 으로 지연 반환을 한다.


List<int> list = new List<int>();

list.Add(1);
list.Add(2);
list.Add(3);

var result = list.Where2(i => i > 1);

foreach (var item in result) { }//result 2, 3

list.Add(4);

foreach (var item in result) { }//result 2, 3, 4

C# async await 를 이용한 안전한 WinForm Control 호출

Button을 클릭 하면 어떤 작업을 실행하고 실행 과정을 ProgressBar에 표시 한다고 가정하자.

이전 사용하던 번거로운 비동기 처리 작업과 Control.Invoke() 메서드를 사용하지 않고
async await 를 사용하여 간편하게 비동기 처리가 가능 하다.


private async void button1_Click(object sender, EventArgs e)
{
    int taskCount = 10;

    this.progressBar1.Minimum = 0;
    this.progressBar1.Maximum = taskCount;
    this.progressBar1.Value = 0;

    for (int i = 0; i < taskCount; i++)
    {
        await this.SometingAsync();

        this.progressBar1.Value++;
    }
}

private async Task SometingAsync()
{
    await Task.Delay(2000);
}

C# 문자열 포함 여부 확인하기.

ToUpper() 를 사용하면 불필요한 문자열을 생성하므로 좋은 방법은 아니다. string text = "This is an apple." ; string apple = "Apple." ; bool ...