성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] VT sequences to "CONOUT$" vs. STD_O...
[정성태] NetCoreDbg is a managed code debugg...
[정성태] Evaluating tail call elimination in...
[정성태] What’s new in System.Text.Json in ....
[정성태] What's new in .NET 9: Cryptography ...
[정성태] 아... 제시해 주신 "https://akrzemi1.wordp...
[정성태] 다시 질문을 정리할 필요가 있을 것 같습니다. 제가 본문에...
[이승준] 완전히 잘못 짚었습니다. 댓글 지우고 싶네요. 검색을 해보...
[정성태] 우선 답글 감사합니다. ^^ 그런데, 사실 저 예제는 (g...
[이승준] 수정이 안되어서... byteArray는 BYTE* 타입입니다...
글쓰기
제목
이름
암호
전자우편
HTML
홈페이지
유형
제니퍼 .NET
닷넷
COM 개체 관련
스크립트
VC++
VS.NET IDE
Windows
Team Foundation Server
디버깅 기술
오류 유형
개발 환경 구성
웹
기타
Linux
Java
DDK
Math
Phone
Graphics
사물인터넷
부모글 보이기/감추기
내용
<div style='display: inline'> <h1 style='font-family: Malgun Gothic, Consolas; font-size: 20pt; color: #006699; text-align: center; font-weight: bold'>WPF - ICommand 동작 방식</h1> <p> 그러고 보니, 제 블로그에서 ICommand를 다룬 적이 한번도 없군요. ^^;<br /> <br /> 이에 대해서는, 일단 다음의 글이 시작점으로 좋습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > ICommand is like a chocolate cake ; http://blogs.msdn.com/b/mikehillberg/archive/2009/03/20/icommand-is-like-a-chocolate-cake.aspx </pre> <br /> 정리해 보면, ICommand는 3개의 멤버를 포함하는 간단한 인터페이스입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > public interface ICommand { void Execute(object parameter); bool CanExecute(object parameter); event EventHandler CanExecuteChanged; } </pre> <br /> 인터페이스니까, ICommand가 의미가 있으려면 호출하는 측과 호출당하는 측 모두 ICommand에 대한 계약을 완료해야 합니다.<br /> <br /> 우선, 호출당하는 측은 ICommand를 구현한 클래스를 정의하고 이에 대한 인스턴스를 호출하는 측으로 전달해야 합니다. 대충 다음과 같이 구현할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > using System; using System.Windows.Input; class Program { static void Main(string[] args) { RelayCommand cmd = new RelayCommand(); } } public class RelayCommand : <span style='color: blue; font-weight: bold'>ICommand</span> { public event EventHandler CanExecuteChanged; public bool CanExecute(object parameter) { return true; } <span style='color: blue; font-weight: bold'>public void Execute(object parameter)</span> { Console.WriteLine(DateTime.Now + ": RelayCommand.Execute - " + parameter); } } </pre> <br /> 그럼, 호출하는 측과 함께 ICommand 인터페이스를 이용해 서로 연동하는 부분도 구현해 보겠습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > using System; using System.Windows.Input; class Program { static void Main(string[] args) { RelayCommand cmd = new RelayCommand(); ConsolePrompt prompt = new ConsolePrompt(); <span style='color: blue; font-weight: bold'>prompt.Command = cmd;</span> prompt.Run(); } } public class ConsolePrompt { <span style='color: blue; font-weight: bold'>public ICommand Command</span> { get; set; } public void Run() { while (true) { string txt = Console.ReadLine(); if (string.IsNullOrEmpty(txt) == true) { break; } <span style='color: blue; font-weight: bold'>Command?.Execute(txt);</span> } } } </pre> <br /> 간단하죠? ^^ 저런 식으로 ICommand 인터페이스가 서로 다른 객체 간에 명령 전달 및 처리를 구현할 수 있습니다. ICommand의 구현 의미는, 위에서 보는 바와 같이 ConsolePrompt로부터 "실행해야 할 코드"를 외부로 분리했다는 정도입니다. 물론, 기존에도 delegate를 전달하거나 event를 정의함으로써 유사한 구현을 했습니다. 단지, ICommand는 이것을 인터페이스로 체계화했을 뿐입니다. 따라서, ICommand를 사용하는 WPF 계열 응용 프로그램을 만드는 경우가 아니라면 사실 알 필요는 거의 없습니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 위에서 구현하지 않은 ICommand.CanExecute에 좀 더 살을 붙여볼까요? 가령 하루 중 오전 9시 ~ 10시 사이에는 명령어 처리를 할 수 없다고 가정해 다음과 같이 코딩할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > public class ConsolePrompt { // ... [생략] ... public void Run() { while (true) { string txt = Console.ReadLine(); if (string.IsNullOrEmpty(txt) == true) { break; } <span style='color: blue; font-weight: bold'>if (Command?.CanExecute(null) == true)</span> { Command?.Execute(txt); } } } } public class RelayCommand : ICommand { public event EventHandler CanExecuteChanged; public bool CanExecute(object parameter) { <span style='color: blue; font-weight: bold'>return DateTime.Now.Hour != 9;</span> } public void Execute(object parameter) { Console.WriteLine(DateTime.Now + ": RelayCommand.Execute - " + parameter); } } </pre> <br /> 즉, 호출하는 측에서도 CanExecute를 호출해 상황 파악을 하는 배려가 있어야 상호 동작이 의도한대로 잘 되는 것입니다.<br /> <br /> 그렇다면 CanExecuteChanged는 언제 사용할까요?<br /> <br /> 위에서 ConsolePrompt는 명령을 실행하기 전 매번 CanExecute를 호출해 명령 처리가 가능한지를 조회해야 했는데요. 이를 CanExecute를 호출해야 할 필요가 있을 때에만, 즉 객체의 내부 환경에서 Command 실행에 영향을 주는 상태가 변화한 딱 그 시점에만 호출하도록 할 수 있습니다.<br /> <br /> 콘솔 예제라서 다소 억지스럽지만 타이머를 이용해 8시에서 9시로, 9시에서 10시로 변경하는 그 시점에만 ConsolePrompt가 CanExecute로 조회하도록 다음과 같이 바꿀 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > public class ConsolePrompt { public ICommand Command { get; set; } public void Run() { <span style='color: blue; font-weight: bold'>_commandEnabled = Command?.CanExecute(null);</span> <span style='color: blue; font-weight: bold'>Command.CanExecuteChanged += Command_CanExecuteChanged;</span> while (true) { string txt = Console.ReadLine(); if (string.IsNullOrEmpty(txt) == true) { break; } <span style='color: blue; font-weight: bold'>if (_commandEnabled == true) { Command?.Execute(txt); }</span> } } bool _commandEnabled = false; <span style='color: blue; font-weight: bold'>private void Command_CanExecuteChanged(object sender, EventArgs e) { _commandEnabled = Command?.CanExecute(null); }</span> } public class RelayCommand : ICommand { public event EventHandler CanExecuteChanged; public RelayCommand() { Thread t = new Thread((e) => { RelayCommand cmd = e as RelayCommand; int old = DateTime.Now.Hour; while (true) { int current = DateTime.Now.Hour; if ((old == 8 && current == 9) || (old == 9 && current == 10)) { <span style='color: blue; font-weight: bold'>cmd.CanExecuteChanged(cmd, EventArgs.Empty); // 명령어를 처리할 수 있는지에 대한 상태 변화를 알림.</span> } old = current; Thread.Sleep(1000); } }); t.IsBackground = true; t.Start(this); } public bool CanExecute(object parameter) { return DateTime.Now.Hour != 9; } public void Execute(object parameter) { Console.WriteLine(DateTime.Now + ": RelayCommand.Execute - " + parameter); } } </pre> <br /> 위의 코드는 CanExecuteChanged 구현 사례를 보이기 위해 다소 억지스러운 상황을 가정한 것이지만, 어쨌든 기능은 대충 저렇게 한다고 보시면 됩니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 이제 WPF로 이야기를 넘어갈까요?<br /> <br /> 위의 예에서 ICommand 인터페이스를 내부적으로 지원하는 ConsolePrompt와 같은 클래스들이 바로 WPF에서 제공해 주는 각종 컨트롤들입니다. 대표적으로 ButtonBase를 상속받은 것들이 이에 해당하는데, 이들은 모두 Command 속성을 구현하게 됩니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > ButtonBase.Command Property ; <a target='tab' href='https://learn.microsoft.com/en-us/dotnet/api/system.windows.controls.primitives.buttonbase.command'>https://learn.microsoft.com/en-us/dotnet/api/system.windows.controls.primitives.buttonbase.command</a> </pre> <br /> Button 객체들의 Command 속성은 내부적으로 ConsolePrompt가 구현했던 것과 유사한 방식으로 동작합니다.<br /> <br /> 일례로, 다음과 같이 Command 속성에 RelayCommand 객체를 할당해 줄 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // MainWindow.xaml <Window x:Class="WpfApplication1.MainWindow" ...[생략]... xmlns:local="clr-namespace:WpfApplication1" <span style='color: blue; font-weight: bold'>DataContext="{Binding RelativeSource={RelativeSource Self}}"</span> mc:Ignorable="d" Title="MainWindow" Height="350" Width="525"> <Grid> <Button <span style='color: blue; font-weight: bold'>Command="{Binding ClickCommand}"</span> Content="Click" /> </Grid> </Window> </pre> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // MainWindow.xaml.cs using System; using System.Windows; using System.Windows.Input; namespace WpfApplication1 { public partial class MainWindow : Window { <span style='color: blue; font-weight: bold'>ICommand _clickCommand = new RelayCommand(); public ICommand ClickCommand { get { return _clickCommand; } }</span> public MainWindow() { InitializeComponent(); } } public class RelayCommand : ICommand { public event EventHandler CanExecuteChanged; public bool CanExecute(object parameter) { return true; } <span style='color: blue; font-weight: bold'>public void Execute(object parameter)</span> { MessageBox.Show("RelayCommand.Execute called!"); } } } </pre> <br /> UI를 가진 Button의 성격상, 이전에 살펴본 ConsolePrompt 객체보다 ICommand를 더 잘 활용합니다. 일례로 CanExecute 메서드에서 false를 반환하도록 하면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > public class RelayCommand : ICommand { // ...[생략]... public bool CanExecute(object parameter) { <span style='color: blue; font-weight: bold'>return false;</span> } // ...[생략]... } </pre> <br /> Button은 자신의 상태를 비활성화시켜버려 사용자가 누르지 못하게 만듭니다. 그런데, 버튼은 CanExecute를 최초 한번만 실행시켜 상태를 알아본 후 그 다음부터는 호출하지 않도록 되어 있습니다. 대신 CanExecuteChanged 이벤트를 구독하고 있기 때문에 상태가 바뀌는 경우 명시적으로 CanExecuteChanged 이벤트를 통해 알려야 합니다. 예를 들어, 화면에 텍스트 상자가 있어 그 안에 사용자가 텍스트를 입력한 경우에만 Button을 클릭 가능하도록 만들고 싶다면 다음과 같이 할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > <Window x:Class="WpfApplication1.MainWindow" ...[생략]... xmlns:local="clr-namespace:WpfApplication1" DataContext="{Binding RelativeSource={RelativeSource Self}}" mc:Ignorable="d" Title="MainWindow" Height="350" Width="525"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="25" /> <RowDefinition Height="*" /> </Grid.RowDefinitions> <span style='color: blue; font-weight: bold'><TextBox Name="_txtBox" /></span> <Button Grid.Row="1" Command="{Binding ClickCommand}" Content="Click" /> </Grid> </Window> </pre> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > using System; using System.Windows; using System.Windows.Input; namespace WpfApplication1 { public partial class MainWindow : Window { ICommand _clickCommand = new RelayCommand(); public ICommand ClickCommand { get { return _clickCommand; } } public MainWindow() { InitializeComponent(); _txtBox.TextChanged += _txtBox_TextChanged; } private void _txtBox_TextChanged(object sender, System.Windows.Controls.TextChangedEventArgs e) { <span style='color: blue; font-weight: bold'>(_clickCommand as RelayCommand).FireChanged(_txtBox.Text.Length != 0);</span> } } public class RelayCommand : ICommand { <span style='color: blue; font-weight: bold'>bool _canExceute = false;</span> public event EventHandler CanExecuteChanged; public bool CanExecute(object parameter) { return _canExceute; } public void Execute(object parameter) { MessageBox.Show("RelayCommand.Execute called!"); } <span style='color: blue; font-weight: bold'>public void FireChanged(bool cond) { _canExceute = cond; CanExecuteChanged(this, EventArgs.Empty); }</span> } } </pre> <br /> 그런데, 상태가 바뀔 때마다 일일이 저런 식으로 알림 이벤트를 호출하는 것이 여간 귀찮은 작업이 아닐 수 없습니다. 그래서 WPF 수준에서 의존 속성들에 대한 상태 변경을 스스로 알 수 있기 때문에 그런 경우 발생하는 알림 이벤트를 별도로 정의해 두었는데 그것이 바로 CommandManager.RequerySuggested 이벤트입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > CommandManager.RequerySuggested Event ; <a target='tab' href='https://learn.microsoft.com/en-us/dotnet/api/system.windows.input.commandmanager.requerysuggested'>https://learn.microsoft.com/en-us/dotnet/api/system.windows.input.commandmanager.requerysuggested</a> </pre> <br /> 이를 이용하면 RelayCommand를 좀 더 간단하게 구성할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > using System; using System.Windows; using System.Windows.Controls; using System.Windows.Input; namespace WpfApplication1 { public partial class MainWindow : Window { ICommand _clickCommand; public ICommand ClickCommand { get { if (_clickCommand == null) { _clickCommand = new RelayCommand(this._txtBox); } return _clickCommand; } } public MainWindow() { InitializeComponent(); } } public class RelayCommand : ICommand { TextBox _target; public RelayCommand(TextBox txtBox) { _target = txtBox; } <span style='color: blue; font-weight: bold'> public event EventHandler CanExecuteChanged { add { CommandManager.RequerySuggested += value; } remove { CommandManager.RequerySuggested -= value; } }</span> public bool CanExecute(object parameter) { return string.IsNullOrEmpty(_target.Text) == false; } public void Execute(object parameter) { MessageBox.Show("RelayCommand.Execute called!"); } } } </pre> <br /> Button 객체는 Command 속성에 ICommand 상속 객체가 할당되면 그 인스턴스의 CanExecuteChanged 이벤트를 구독합니다. 따라서, 위의 코드에서는 Button 객체가 CanExecuteChanged.add를 호출하게 되고 Button 측에서 전달한 이벤트 핸들러 메서드를 가리키는 value는 다시 CommandManager.RequerySuggested 이벤트로 전달하게 되므로 자연스럽게 WPF 내부에서 RequerySuggested 이벤트를 발생시킬 때마다 버튼은 CanExecuteChanged 알림을 받게 되고 이어서 CanExecute를 호출해 객체의 상태를 조회하게 됩니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 그렇다면 WPF의 RoutedCommand는 도대체 뭘까요?<br /> <br /> 이 글에서 우리는 ICommand 객체를 구현한 RelayCommand를 사용하고 있는데요. 마찬가지로 WPF는 스스로 사용할 목적으로 RoutedCommand를 구현해 놓은 것 뿐입니다. 즉, ICommand의 구현체 중 하나에 속합니다.<br /> <br /> 그럼, RoutedUICommand는 또 뭘까요? 이에 대해서는 다음의 글에 잘 설명되어 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > WPF - RoutedCommand vs.RoutedUICommand ; <a target='tab' href='http://compilewith.net/2008/03/wpf-routedcommand-vsrouteduicommand.html'>http://compilewith.net/2008/03/wpf-routedcommand-vsrouteduicommand.html</a> </pre> <br /> RoutedUICommand는 RoutedCommand를 상속받은 것으로 역시 WPF가 구현해 놓고 있습니다. 그리고 그 차이점은 Text 속성을 갖는다는 것 뿐입니다. 이것이 언제 쓰이냐면??? 바로 MenuItem같은 HeaderedItemsControl을 상속받은 객체들이 Command에 RoutedUICommand를 설정하면 그 MenuItem의 Header값으로 RoutedUICommand.Text 값을 사용하게 됩니다.<br /> <br /> 간단한 예를 만들어 보면!<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > <Window x:Class="WpfApplication1.MainWindow" ...[생략]... xmlns:local="clr-namespace:WpfApplication1" DataContext="{Binding RelativeSource={RelativeSource Self}}" mc:Ignorable="d" Title="MainWindow" Height="350" Width="525"> <Grid> <Menu> <Menu.CommandBindings> <CommandBinding Command="local:MainWindow.CopyCommand" Executed="CopyCommand_Executed" CanExecute="CopyCommand_CanExecute" /> </Menu.CommandBindings> <MenuItem Header="Menu1"> <span style='color: blue; font-weight: bold'><MenuItem Command="local:MainWindow.CopyCommand" /></span> </MenuItem> </Menu> </Grid> </Window> </pre> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > using System; using System.Windows; using System.Windows.Controls; using System.Windows.Input; namespace WpfApplication1 { public partial class MainWindow : Window { public static readonly RoutedUICommand CopyCommand = new RoutedUICommand(<span style='color: blue; font-weight: bold'>"MyCopy"</span>, "CopyFunc", typeof(MainWindow), null); public MainWindow() { InitializeComponent(); } private void CopyCommand_CanExecute(object sender, CanExecuteRoutedEventArgs e) { e.CanExecute = true; } private void CopyCommand_Executed(object sender, ExecutedRoutedEventArgs e) { MessageBox.Show("RoutedUICommand.Execute called!"); } } } </pre> <br /> 보는 바와 같이 <MenuItem Command="local:MainWindow.CopyCommand" /> 에는 Header 속성이 정의되어 있지 않지만, 그것의 Command로 할당한 RoutedUICommand 객체의 생성자에 전달된 "MyCopy"라는 문자열이 있기 때문에 메뉴가 보여지는 경우 "MyCopy"로 보이게 됩니다. 물론, <MenuItem <span style='color: blue; font-weight: bold'>Header="MyCopy2"</span> Command="local:MainWindow.CopyCommand" />라고 명시적인 Header 값을 부여하는 것도 가능합니다. 이런 경우 RoutedUICommand에 설정된 값을 무시합니다. <br /> <hr style='width: 50%' /><br /> <br /> ICommand가 뜨는 이유는, 이것을 활용하는 경우 멋있는 MVVM(Model-View-ViewModel) 구조의 응용 프로그램 작성이 가능하기 때문입니다. 이에 대해서는 이야기가 또 한가득이니 일단 접고.<br /> <br /> 문제는, 기존 컨트롤들이 정의한 이벤트들이 모두 ICommand로 제공하지는 않는다는 점입니다. 일례로, Window의 크기가 변경되는 SizeChanged 이벤트에 대한 ICommand 속성은 없습니다. 애당초 SizeChangedCommand라는 속성으로 이벤트 대신 ICommand용 속성이 제공된다면 좋을 텐데 모든 이벤트를 그렇게 중복 정의해 놓는 것도 그리 바람직하지는 않습니다. 게다가, 이벤트라는 것은 '알림'에 가까운 특징이 있는 반면 ICommand는 말 그대로 '명령 실행'이라는 점에서 그 구현 배경이 달라지는 점도 있습니다.<br /> <br /> 하지만, MVVM의 ViewModel을 이용해 UI와 코드의 분리를 확실하게 이루기 위해서는 기존 이벤트들에 대한 ICommand로의 처리 방안이 필요합니다. 이 때문에 나온 것들이 바로 다양한 MVVM 프레임워크에서 제공해주는 EventToCommand 같은 구현체들입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > MVVM - Commands, RelayCommands and EventToCommand ; <a target='tab' href='https://learn.microsoft.com/en-us/archive/msdn-magazine/2013/may/mvvm-commands-relaycommands-and-eventtocommand'>https://learn.microsoft.com/en-us/archive/msdn-magazine/2013/may/mvvm-commands-relaycommands-and-eventtocommand</a> </pre> <br /> 이에 대한 고민을 좀 더 확실히 느끼고 싶은 분들은 다음의 4부작 글을 추천해드립니다. ^^<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > View의 Event를 ViewModel에서 핸들링하기 - 개요 WPF & C# ; <a target='tab' href='http://blog.naver.com/vactorman/220516516289'>http://blog.naver.com/vactorman/220516516289</a> View의 Event를 ViewModel에서 핸들링하기 - EventTrigger ; <a target='tab' href='http://blog.naver.com/vactorman/220516524223'>http://blog.naver.com/vactorman/220516524223</a> View의 Event를 ViewModel에서 핸들링하기 - ACB ; <a target='tab' href='http://blog.naver.com/vactorman/220516529243'>http://blog.naver.com/vactorman/220516529243</a> View의 Event를 ViewModel에서 핸들링하기 - Markup ; <a target='tab' href='http://blog.naver.com/vactorman/220516534284'>http://blog.naver.com/vactorman/220516534284</a> EventToCommand를 이용한 ViewModel 이벤트 핸들링 ; <a target='tab' href='http://blog.naver.com/vactorman/221064433289'>http://blog.naver.com/vactorman/221064433289</a> </pre> <br /> 이 정도면 웬만큼 정리된 것 같군요. ^^<br /> <br /> (<a target='tab' href='http://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=994&boardid=331301885'>첨부한 파일은 이 글의 예제 코드를 포함</a>합니다.)<br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
4236
(왼쪽의 숫자를 입력해야 합니다.)