2024년 3월 26일 화요일

Avalonia Weston 지원 확인

X11 이 아닌 Weston(Wayland reference compositor) 에서 Avalonia 로 빌드한 프로그램이 실행가능함을 확인하였다. 

실행환경 : i.MX8 시리즈 CPU, Debian 11, Weston

sudo apt install xwayland => 반드시 설치
export DISPLAY=:0 => 환경변수에 추가








2024년 2월 28일 수요일

C++ 우측값 참조와 std::move 의 실제 동작 원리

 우측값 참조에 대해 검색해보면 보통 아래와 같이 설명한다.

1. 좌측값/우측값에 대한 설명
2. 좌측값 참조/우측값 참조에 대한 설명
3. std::move 를 통한 자원 이동에 대한 설명

설명을 읽어보고 대략적인 의미와 적용방법은 알 수 있었으나 실체적인 동작원리를 알 수 없었다.
예를 들어 std::move 를 사용하면 해당 변수를 좌측값->우측값 으로 변환한다 고 설명하는데 구체적인 설명은 보통 없다.

먼저 우측값 참조를 간단하게 설명하자면,

int a = 10;
int b = 20;
int&& c = a + b;

위와같은 코드가 있을때 마지막 줄에서 메모리 상에 어떤 일이 발생하는가하면
CPU가 a+b(30)를 계산한 후 해당 값을 임시 메모리 변수에 저장을 하게되고 우측값 참조변수 c가 이 메모리를 참조하게된다.
int c = a + b; 였으면 컴파일러에 따라 생성되지않거나 혹은 바로 사라질 임시 변수(우측값)를 해제하지 않고 c가 참조하게 함으로써 메모리에서 해제를 하지않게된다.
사실 int같은 기본타입에서는 별다른 사용 용도가 없으며 실제로는 클래스(구조체)에서 사용된다.

먼저 std::move 함수를 보자.
std::move(변수) 는 static_cast<변수타입&&>(변수) 와 같은 의미이다. 그러니까 그냥 우측값 참조형으로 타입 캐스팅을 하는것이지
실제로 메모리 상에 뭐가 이동하고 어쩌고 그런것과 아무 상관이 없다.

그리고 우측값 참조는 int 같은 기본형에서는 메모리 이동의 효과가 전혀 없다.(당연하게도)
아래 코드와 같이 클래스(구조체)인 경우에 우측값 참조를 받는 이동 생성자/이동 대입 연산자에 우측값 참조를 받아서
깊은 복사없이 포인터만 변경하게 해주는 것으로 그 용도가 전부이다.

 class MoveTest  
 {  
 private:  
   char* str = nullptr;  
   
 public:  
   MoveTest()  
   {  
     str = nullptr;  
   }  
   
   MoveTest(const char* str1)  
   {  
     int len = strlen(str1);  
     str = new char[len + 1];  
     strcpy_s(str, len+1, str1);  
   }  
   
   MoveTest(const MoveTest& lhs)  
   {  
     int len = strlen(lhs.str);  
     str = new char[len + 1];  
     strcpy_s(str, len+1, lhs.str);  
   }  
   
   // 이동 생성자  
   MoveTest(MoveTest&& rhs) noexcept : str(rhs.str)  
   {  
     rhs.str = nullptr;  
   }  
   
   MoveTest& operator=(const MoveTest& lhs)  
   {  
     int len = strlen(lhs.str);  
     str = new char[len + 1];  
     strcpy_s(str, len + 1, lhs.str);  
     return *this;  
   }  
   
   // 이동 대입 연산자  
   MoveTest& operator=(MoveTest&& rhs) noexcept  
   {  
     str = rhs.str;  
     rhs.str = nullptr;  
     return *this;  
   }  
   
   ~MoveTest()  
   {  
     delete[] str;  
   }  
 };  
위 코드에서 

MoveTest m1("abc");
MoveTest m2(std::move(m1)); // 이동 생성자 호출, m1->m2 로 이동
// MoveTest m2(static_cast<MoveTest&&>(m1)); 와 같음

MoveTest m3;
m3 = std::move(m2); // 이동 대입 연산자 호출, m2->m3 로 이동
// m3 = static_cast<MoveTest&&>(m2); 와 같음

와 같이 해당 이동 생성자/이동 대입 연산자를 호출하게되고 호출을 받은쪽은 포인터 이동을 하면 되는것이다.

그러면 왜 하필 굳이 std::move 를 통해 우측값 참조 타입으로 이 기능을 구현하는가?
그냥 위 코드에서 참조(const MoveTest& lhs)를 받는 복사 생성자와 대입 연산자에서도 처리가 가능할텐데 말이다.
몇 가지 이유가 있지만 제일 큰 이유는 자원이 이동한다는 것을 명시적으로 표현함으로써 표준을 정하고 혼란을 없애기 위함이다.
일반 복사 생성자/대입 연산자에서 이것을 처리하게 되면 실제 깊은 복사가 일어나야 하는 것인지 자원이동만 일어나야하는지 구분이 어렵기 때문이다.

2024년 2월 5일 월요일

윈도우 x265 빌드

* nasm 설치
nasm 설치 후 환경변수에 경로 추가 (https://www.nasm.us/) (구글에서 nasm download 검색)

* cmake 설치
구글에서 windows cmake 검색(https://cmake.org/download/)

* x265 소스 다운로드
git clone https://bitbucket.org/multicoreware/x265_git.git

* 32 비트 빌드
Visual Studio 메뉴에서 x86 Native Tools Command Prompt for VS 2022 실행

x265_git\build\vc15-x86 에서 아래실행
cmake -G "Visual Studio 17 2022" -T v143 -A Win32 ..\..\source && cmake-gui ..\..\source

cmake-gui 뜨면 아래 Configure 클릭

x265.sln VS2022 로 열어서 빌드하면 x265_git\build\vc15-x86\Release(Debug) 아래에 빌드 파일 생성됨

* 64 비트 빌드
Visual Studio 메뉴에서 x64 Native Tools Command Prompt for VS 2022 실행

x265_git\build\vc15-x86_64 에서 아래실행
cmake -G "Visual Studio 17 2022" -T v143 ..\..\source && cmake-gui ..\..\source

cmake-gui 뜨면 아래 Configure 클릭

x265.sln VS2022 로 열어서 빌드하면 x265_git\build\vc15-x86_64\Release(Debug) 아래에 빌드 파일 생성됨

2024년 2월 1일 목요일

윈도우에서 ffmpeg NVIDIA CUDA Codec 연동 build

 * msys 설치



* Visual Studio 메뉴에서 x64 Native Tools Command Prompt for VS 2022 실행

c:\msys64\msys2_shell.cmd -use-full-path 실행

빌드에 필요한 패키지들을 설치한다.

pacman -Syu
pacman -S make diffutils yasm nasm git pkg-config

where link 하면 아래 두개가 나온다.

C:\msys64\usr\bin\link.exe
C:\Program Files\Microsoft Visual Studio\2022\Professional\VC\Tools\MSVC\<버전>\bin\Hostx64\x64\link.exe

mv /usr/bin/link.exe /usr/bin/link.exe.org => 겹치지 않게 이름 변경


* 소스 다운로드

git clone https://git.videolan.org/git/ffmpeg/nv-codec-headers.git

nv-codec-headers 폴더 들어가서 아래 실행
make install PREFIX=/usr

ffmpeg 소스 다운로드
git clone https://git.ffmpeg.org/ffmpeg.git


* CUDA Toolkit 설치
https://developer.nvidia.com/cuda-toolkit 에서 툴킷 받아서 설치한다.(혹은 구글에서 cuda toolkit 검색)

홈 디렉토리에서 아래 폴더 생성
mkdir build
mkdir nv_sdk

C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\<버전> 아래에 include 폴더 복사 후 nv_sdk 아래에 불여넣기
C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\<버전>\lib 아래에 x64 폴더 복사 후 nv_sdk 아래에 붙여넣기


* 빌드

export PATH="/c/Program Files/NVIDIA GPU Computing Toolkit/CUDA/<버전>/bin/":$PATH

ffmpeg 폴더에 들어가서 아래 실행

./configure --prefix=../build --toolchain=msvc --arch=x86_64 --enable-yasm --enable-shared --enable-gpl --enable-nonfree --enable-w32threads --enable-cuda-nvcc --enable-libnpp --extra-cflags=-I../nv_sdk/include --extra-ldflags=-libpath:../nv_sdk/x64

make
make install



* 32비트 빌드

Visual Studio 메뉴에서 x86 Native Tools Command Prompt for VS 2022 실행 후 c:\msys64\msys2_shell.cmd -use-full-path 실행

./configure --prefix=../build --toolchain=msvc --arch=x86_32 --enable-yasm --enable-shared --enable-gpl --enable-nonfree --enable-w32threads

* x264 빌드 후 ffmpeg 연동 빌드

32/64 비트는 각각 
32비트 => x86 Native Tools Command Prompt for VS 2022 실행 후 c:\msys64\msys2_shell.cmd -use-full-path 실행
64비트 => x64 Native Tools Command Prompt for VS 2022 실행 후 c:\msys64\msys2_shell.cmd -use-full-path 실행

소스 다운로드
git clone http://git.videolan.org/git/x264.git

x264 폴더에서 아래 실행 - 32/64비트 공통
CC=cl ./configure --prefix=../build --enable-static
 
make
make install

export PKG_CONFIG_PATH=../build/lib/pkgconfig/

ffmpeg 폴더에서 아래 실행
64비트
./configure --prefix=../build --toolchain=msvc --arch=x86_64 --enable-yasm --enable-shared --enable-gpl --enable-nonfree --enable-w32threads --enable-cuda-nvcc --enable-libnpp --extra-cflags="-I../nv_sdk/include -I../build/include" --extra-ldflags="-libpath:../nv_sdk/x64 -libpath:../build/lib" --enable-libx264

32비트
./configure --prefix=../build --toolchain=msvc --arch=x86_32 --enable-yasm --enable-shared --enable-gpl --enable-nonfree --enable-w32threads  --extra-cflags="-I../build/include" --extra-ldflags="-libpath:../build/lib" --enable-libx264

make
make install

2023년 12월 8일 금요일

Avalonia ImageButton source code

 github link : https://github.com/kim-dong-hyun/AvaloniaImageButton








2023년 11월 24일 금요일

Rendering Video in Avalonia's NativeControlHost with SDL2.

 Add a NativeEmbeddingControl class that inherits from NativeControlHost in MainWindow.axaml.cs

 namespace AvaloniaDXPlayer  
 {  
   public partial class MainWindow : Window  
   {  
     public MainWindow()  
     {  
       InitializeComponent();  
     }  
     ...  
   public class NativeEmbeddingControl : NativeControlHost  
   {  
     public IntPtr Handle { get; private set; }  
   
     protected override IPlatformHandle CreateNativeControlCore(IPlatformHandle parent)  
     {  
       var handle = base.CreateNativeControlCore(parent);  
       Handle = handle.Handle;  
       Console.WriteLine($"Handle : {Handle}");  
       return handle;  
     }  
   }     
 }  


Add a NativeEmbeddingControl class that inherits from NativeControlHost in MainWindow.axaml.cs

 <Window xmlns="https://github.com/avaloniaui"  
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"  
     xmlns:d="http://schemas.microsoft.com/expression/blend/2008"  
     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"  
     xmlns:nec="clr-namespace:AvaloniaDXPlayer"  
     mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="600"  
     x:Class="AvaloniaDXPlayer.MainWindow"  
     Width="800" Height="600" Title="AvaloniaDXPlayer">  
  <Grid>  
   <Grid.RowDefinitions>  
    <RowDefinition Height="*"/>  
    <RowDefinition Height="100"/>  
   </Grid.RowDefinitions>  
   <Grid Grid.Row="0" Background="Black">  
    <nec:NativeEmbeddingControl x:Name="host" Grid.Row="0" SizeChanged="Host_SizeChanged" />  
   </Grid>  
   <Canvas Grid.Row="1">  
    <TextBox x:Name="textURL" Margin="10" Width="300" Height="20" FontSize="14" Text=""/>  
    <Button x:Name="btnConnect" Content="Connect" Margin="320,10,0,0" />  
    <Button x:Name="btnClose" Content="Close" Margin="400,10,0,0" />  
    <Button x:Name="btnTest" Content="Test" Margin="460,10,0,0" />  
   </Canvas>  
  </Grid>  
 </Window>  
   


Initialize SDL2. During this, insert the Handle of NativeEmbeddingControl as an argument into SDL_CreateWindowFrom

 
#if defined(LINUX)
typedef void*              HWND;  
#endif
 ...  
 int VideoSDLDraw::init(HWND hwnd, int targetWidth, int targetHeight, int srcWidth, int srcHeight)  
 {  
      release();  
   
      DXPRINTF("VideoSDLDraw::init %dx%d, %dx%d\n", targetWidth, targetHeight, srcWidth, srcHeight);  
   
      MUTEX_LOCK(&m_mutex);  
   
      Uint32 pixelFormat = SDL_PIXELFORMAT_UNKNOWN;  
   
      m_pWindow = SDL_CreateWindowFrom(hwnd);  
      if (m_pWindow == NULL) {  
           DXPRINTF("Window could not be created! SDL Error: %s\n", SDL_GetError());  
           goto fail;  
      }  
   
      m_pRenderer = SDL_CreateRenderer(m_pWindow, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);  
      if (m_pRenderer == NULL) {  
           DXPRINTF("Renderer could not be created! SDL Error: %s\n", SDL_GetError());  
           m_nTextureWidth = m_nTextureHeight = 0;  
           goto fail;  
      }  
      else {  
           //Initialize renderer color  
           SDL_SetRenderDrawColor(m_pRenderer, 0x00, 0x00, 0x00, 0xFF);  
           for (int i = 0; i < m_nTextDraw; i++) m_pTextDraw[i]->setSDLRenderer(m_pRenderer);  
      }  
 #if defined(LINUX)  
      if (targetWidth == 0 && targetHeight == 0) {  
           SDL_GetWindowSize(m_pWindow, &targetWidth, &targetHeight);  
      }  
 #endif  
      pixelFormat = SDL_GetWindowPixelFormat(m_pWindow);  
   
      m_pTexture = SDL_CreateTexture(m_pRenderer, pixelFormat, SDL_TEXTUREACCESS_STREAMING, targetWidth, targetHeight);  
      if (m_pTexture == NULL) {  
           DXPRINTF("Unable to create streamable texture! SDL Error: %s\n", SDL_GetError());  
           m_nTextureWidth = m_nTextureHeight = 0;  
           goto fail;  
      }  
   
      m_nTextureWidth = targetWidth;  
      m_nTextureHeight = targetHeight;  
   
      SetRectEmpty(&m_rectTargetLast);  
   
      SDL_RendererInfo rendererInfo;  
      SDL_GetRendererInfo(m_pRenderer, &rendererInfo);  
   
      DXPRINTF("SDL Renderer : %s\n", rendererInfo.name);  
   
      MUTEX_UNLOCK(&m_mutex);  
      return 0;  
   
 fail:  
      MUTEX_UNLOCK(&m_mutex);  
      return -1;  
 }  

After initialization as described above, render the decoded video data on m_pTexture with SDL2 rendering function. 
The result is as shown in the figure below.

Linux







Windows





2023년 11월 6일 월요일

2가지 예제를 통한 간단한 WPF 바인딩 원리


바인딩의 기본 개념은 위 그림과 같다. 
바인딩 타겟은 항상 DependencyProperty 여야하고 바인딩 소스는 DependencyProperty 나 일반 프로퍼티(.NET 프로퍼티)나 상관없다.
여기서

OneWay 바인딩은 소스 -> 타겟 
OneWayToSource 바인딩은 소스 <- 타겟
TwoWay 바인딩은 소스 <-> 타겟

으로 바인딩 된다.


1. TextBox 바인딩

화면을 위아래로 나눠서 상단에 텍스트 박스를 하단에 버튼 1개을 나타낸 간단한 UI를 만든다.

 <Window x:Class="TextBoxBind.MainWindow"  
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"  
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"  
     xmlns:d="http://schemas.microsoft.com/expression/blend/2008"  
     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"  
     xmlns:local="clr-namespace:TextBoxBind"  
     mc:Ignorable="d"  
     Title="MainWindow" Height="450" Width="800">  
   <Grid>  
     <Grid.RowDefinitions>  
       <RowDefinition Height="50*"/>  
       <RowDefinition Height="50*"/>  
     </Grid.RowDefinitions>  
     <TextBox Grid.Row="0" Text="{Binding InputText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />  
     <Button x:Name="button" Content="Add Text" HorizontalAlignment="Left" Margin="42,29,0,0" Grid.Row="1" VerticalAlignment="Top" Click="button_Click"/>  
   </Grid>  
 </Window>  

위 xaml 코드에서 TextBox는 InputText 프로퍼티와 바인딩을 하며 Mode는 TwoWay, UpdateSourceTrigger는 PropertyChanged 이다.
즉 양방향 바인딩이고 데이터 변환시 즉시 반영되는 바인딩이다.
code behind 는 아래와 같다.

 using System;  
 using System.Collections.Generic;  
 using System.ComponentModel;  
 using System.Linq;  
 using System.Runtime.CompilerServices;  
 using System.Text;  
 using System.Threading;  
 using System.Threading.Tasks;  
 using System.Windows;  
 using System.Windows.Controls;  
 using System.Windows.Data;  
 using System.Windows.Documents;  
 using System.Windows.Input;  
 using System.Windows.Media;  
 using System.Windows.Media.Imaging;  
 using System.Windows.Navigation;  
 using System.Windows.Shapes;  
   
 namespace TextBoxBind  
 {  
   /// <summary>  
   /// MainWindow.xaml에 대한 상호 작용 논리  
   /// </summary>  
   public partial class MainWindow : Window, INotifyPropertyChanged  
   {  
     private string inputText;  
   
     public string InputText  
     {  
       get { return inputText; }  
       set  
       {  
         if (inputText != value)  
         {  
           Console.WriteLine($"InputText : {value}");  
           inputText = value;  
           OnPropertyChanged("InputText");
         }  
       }  
     }  
   
     public MainWindow()  
     {  
       InitializeComponent();  
       DataContext = this;  
     }  
   
     public event PropertyChangedEventHandler PropertyChanged;  
   
     protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)  
     {  
       PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));  
     }  
   
     private void button_Click(object sender, RoutedEventArgs e)  
     {  
       Thread thread = new Thread(new ThreadStart(() => InputText += "AAA"));  
       thread.IsBackground = true;  
       thread.Start();  
     }  
   }  
 }  
   

Add Text 버튼클릭시 쓰레드로 InputText 프로퍼티의 값을 추가한다.
양방향 바인딩이므로 TextBox에 문자를 입력하면 InputText 값이 바뀌고 버튼을 클릭하면 TextBox 출력문자가 변경된다.('AAA'추가)

여기서 InputText의 setter 부분에 있는 OnPropertyChanged("InputText") 
를 주석처리하면 버튼을 클릭해도 TextBox에 수정내용 즉 InputText 값이 반영되지 않는다.

이유는 바인딩을 하는 순간 TextBox는 
public event PropertyChangedEventHandler PropertyChanged; 
델러게이트 이벤트 핸들러를 구독하게되고 이벤트 발생시 InputText의 getter를 통해 현재 값을 가져와서 UI에 반영(Text 프로퍼티에)하기 때문이다.

Mode=OneWayToSource 를 하면 바인딩 타겟(TextBox) -> 바인딩 소스(InputText) 로만 데이터 전달이 되는데 이 경우는 TextBox가 바로 InputText의 setter 를 호출하게 되는데 이때는 OnPropertyChanged("InputText") 호출이 필요없다.(다른 이유에서 필요할 수 있으므로 호출하는것이 좋다)


2. DataGrid 바인딩

화면을 좌우로 나눠서 왼쪽에 DataGrid 를 오른쪽에 버튼과 기타 텍스트박스 컨트롤을 배치했다.

 <Window x:Class="ListBindTest.MainWindow"  
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"  
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"  
     xmlns:d="http://schemas.microsoft.com/expression/blend/2008"  
     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"  
     xmlns:local="clr-namespace:ListBindTest"  
     mc:Ignorable="d"  
     Title="MainWindow" Height="450" Width="800">  
   <Grid>  
     <Grid.ColumnDefinitions>  
       <ColumnDefinition Width="*"/>  
       <ColumnDefinition Width="*"/>  
     </Grid.ColumnDefinitions>  
     <DataGrid Grid.Column="0" ItemsSource="{Binding People}" SelectedItem="{Binding SelectedItem}" SelectionMode="Single" />  
     <Canvas Grid.Column="1">  
       <TextBlock x:Name="textBlock" Canvas.Left="41" TextWrapping="Wrap" Text="Name" Canvas.Top="35"/>  
       <TextBlock x:Name="textBlock_Copy" Canvas.Left="41" TextWrapping="Wrap" Text="Age" Canvas.Top="87" HorizontalAlignment="Center" VerticalAlignment="Top"/>  
       <TextBox x:Name="textName" Canvas.Left="101" Text="{Binding SelectedItem.Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Canvas.Top="35" Width="120"/>  
       <TextBox x:Name="textAge" Canvas.Left="101" Text="{Binding SelectedItem.Age, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Canvas.Top="87" Width="120" HorizontalAlignment="Center" VerticalAlignment="Top"/>  
       <TextBlock x:Name="textBlock_Copy1" Canvas.Left="41" TextWrapping="Wrap" Text="Name" Canvas.Top="183" HorizontalAlignment="Center" VerticalAlignment="Top"/>  
       <TextBlock x:Name="textBlock_Copy2" Canvas.Left="41" TextWrapping="Wrap" Text="Age" Canvas.Top="235" HorizontalAlignment="Center" VerticalAlignment="Top"/>  
       <Canvas x:Name="canvasTest">  
         <TextBox x:Name="textName_Copy" Canvas.Left="101" Text="{Binding Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Canvas.Top="183" Width="120" HorizontalAlignment="Center" VerticalAlignment="Top"/>  
         <TextBox x:Name="textAge_Copy" Canvas.Left="101" Text="{Binding Age, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Canvas.Top="235" Width="120" HorizontalAlignment="Center" VerticalAlignment="Top"/>  
       </Canvas>  
       <Button x:Name="button" Content="button" Canvas.Left="22" Canvas.Top="297" Click="button_Click"/>  
       <Button x:Name="button1" Content="button1" Canvas.Left="78" Canvas.Top="297" Click="button1_Click"/>  
     </Canvas>  
   </Grid>  
 </Window>     

code behind 는 아래와 같다.
 using System;  
 using System.Collections.Generic;  
 using System.Collections.ObjectModel;  
 using System.ComponentModel;  
 using System.Linq;  
 using System.Runtime.InteropServices;  
 using System.Text;  
 using System.Threading;  
 using System.Threading.Tasks;  
 using System.Windows;  
 using System.Windows.Controls;  
 using System.Windows.Data;  
 using System.Windows.Documents;  
 using System.Windows.Input;  
 using System.Windows.Media;  
 using System.Windows.Media.Imaging;  
 using System.Windows.Navigation;  
 using System.Windows.Shapes;  
   
 namespace ListBindTest  
 {  
   /// <summary>  
   /// MainWindow.xaml에 대한 상호 작용 논리  
   /// </summary>  
   public partial class MainWindow : Window  
   {  
     private MainViewModel viewModel = new MainViewModel();  
     private Person personTest = new Person();  
   
     public MainWindow()  
     {  
       InitializeComponent();  
   
       DataContext = viewModel;  
   
       personTest.Name = "Test";  
       personTest.Age = 100;  
       canvasTest.DataContext = personTest;  
     }  
   
     private void button_Click(object sender, RoutedEventArgs e)  
     {  
       Person p = new Person();  
       p.Name = personTest.Name;  
       p.Age = personTest.Age;  
   
       Thread thread = new Thread(new ThreadStart(() =>  
       {  
         this.Dispatcher.BeginInvoke(new Action(() =>  
         {  
           viewModel.People.Add(p);
         }));  
       }));  
       thread.IsBackground = true;  
       thread.Start();  
     }  
   
     private void button1_Click(object sender, RoutedEventArgs e)  
     {  
       Thread thread = new Thread(new ThreadStart(() =>  
       {  
         Person p = viewModel.SelectedItem;  
         if (p != null) p.Age++;  
       }));  
       thread.IsBackground = true;  
       thread.Start();  
     }  
   }  
   
  public class Person : INotifyPropertyChanged  
   {  
     private string _name;  
     private int _age;  
   
     public string Name  
     {  
       get { return _name; }  
       set  
       {  
         _name = value;  
         OnPropertyChanged(nameof(Name));  
       }  
     }  
   
     public int Age  
     {  
       get { return _age; }  
       set  
       {  
         _age = value;  
         OnPropertyChanged(nameof(Age));  
       }  
     }  
   
     public event PropertyChangedEventHandler PropertyChanged;  
   
     protected virtual void OnPropertyChanged(string propertyName)  
     {  
       PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));  
     }  
   }  
   
   public class MainViewModel : INotifyPropertyChanged  
   {  
     private ObservableCollection<Person> _people;  
     private Person _selectedItem;  
   
     public MainViewModel()  
     {  
       _people = new ObservableCollection<Person>();  
       _people.Add(new Person { Name = "Alice", Age = 25 });  
       _people.Add(new Person { Name = "Bob", Age = 30 });  
       _people.Add(new Person { Name = "Charlie", Age = 35 });  
     }  
   
     public ObservableCollection<Person> People  
     {  
       get { return _people; }  
       set  
       {  
         _people = value;  
         OnPropertyChanged(nameof(People));  
       }  
     }  
   
     public Person SelectedItem  
     {  
       get { return _selectedItem; }  
       set  
       {  
         _selectedItem = value;  
         OnPropertyChanged(nameof(SelectedItem));  
       }  
     }  
   
     public event PropertyChangedEventHandler PropertyChanged;  
   
     protected virtual void OnPropertyChanged(string propertyName)  
     {  
       PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));  
     }  
   }  
 }  
   

button을 클릭하면 DataGrid 에 아이템이 추가되는데 여기서 주의해야할 점은 바인딩된 ObservableCollection 에 아이템을 추가할때 반드시 UI 쓰레드에서 추가해야한다는 점이다.

UI 쓰레드로 추가하지 않으려면 BindingOperations.EnableCollectionSynchronization 메소드를 사용하는 방법이 있다.

 namespace ListBindTest  
 {  
   /// <summary>  
   /// MainWindow.xaml에 대한 상호 작용 논리  
   /// </summary>  
   public partial class MainWindow : Window  
   {  
     private MainViewModel viewModel = new MainViewModel();  
     private Person personTest = new Person();  
   
     public MainWindow()  
     {  
       InitializeComponent();  
   
       DataContext = viewModel;  
   
       personTest.Name = "Test";  
       personTest.Age = 100;  
       canvasTest.DataContext = personTest;  
     }  
   
     private void button_Click(object sender, RoutedEventArgs e)  
     {  
       Person p = new Person();  
       p.Name = personTest.Name;  
       p.Age = personTest.Age;  
   
       Thread thread = new Thread(new ThreadStart(() =>  
       {  
 #if false  
         this.Dispatcher.BeginInvoke(new Action(() =>  
         {  
           viewModel.People.Add(p);  
           //viewModel.People.Add(personTest);  
         }));  
 #else  
         viewModel.People.Add(p);  
 #endif  
       }));  
       thread.IsBackground = true;  
       thread.Start();        
     }  
   
     private void button1_Click(object sender, RoutedEventArgs e)  
     {  
       Thread thread = new Thread(new ThreadStart(() =>  
       {  
         //personTest.Name = personTest.Name + "X";  
         Person p = viewModel.SelectedItem;  
         if (p != null) p.Age++;  
       }));  
       thread.IsBackground = true;  
       thread.Start();  
     }  
   }  
   
  public class Person : INotifyPropertyChanged  
   {  
     private string _name;  
     private int _age;  
   
     public string Name  
     {  
       get { return _name; }  
       set  
       {  
         _name = value;  
         OnPropertyChanged(nameof(Name));  
       }  
     }  
   
     public int Age  
     {  
       get { return _age; }  
       set  
       {  
         _age = value;  
         OnPropertyChanged(nameof(Age));  
       }  
     }  
   
     public event PropertyChangedEventHandler PropertyChanged;  
   
     protected virtual void OnPropertyChanged(string propertyName)  
     {  
       PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));  
     }  
   }  
   
   public class MainViewModel : INotifyPropertyChanged  
   {  
     private ObservableCollection<Person> _people;  
     private Person _selectedItem;  
   
     private object objLock = new object();  
   
     public MainViewModel()  
     {  
       _people = new ObservableCollection<Person>();  
       _people.Add(new Person { Name = "Alice", Age = 25 });  
       _people.Add(new Person { Name = "Bob", Age = 30 });  
       _people.Add(new Person { Name = "Charlie", Age = 35 });  
   
       BindingOperations.EnableCollectionSynchronization(_people, objLock);  
     }  
   
     public ObservableCollection<Person> People  
     {  
       get { return _people; }  
       set  
       {  
         _people = value;  
         OnPropertyChanged(nameof(People));  
       }  
     }  
   
     public Person SelectedItem  
     {  
       get { return _selectedItem; }  
       set  
       {  
         _selectedItem = value;  
         OnPropertyChanged(nameof(SelectedItem));  
       }  
     }  
   
     public event PropertyChangedEventHandler PropertyChanged;  
   
     protected virtual void OnPropertyChanged(string propertyName)  
     {  
       PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));  
     }  
   }  
 }  

그리고 button1을 클릭하면 Age 값이 1증가하는데 이것은 Person 클래스가 INotifyPropertyChanged 를 구현해서 OnPropertyChanged(nameof(Age)) 를 호출하여 역시 1번 예제에서 설명한데로 public event PropertyChangedEventHandler PropertyChanged 를 구독중인 DataGrid 에게 변경여부를 알려주기 때문이다.