Open lsirikh opened 2 years ago
감사합니다.
일단 확인해보곡 있는데요. 어렵네요...ㅠㅠ
최대한 해보고 다시 PR남기도록 하겟습니다.
너무 감사합니다. 쉬시는날에 고생 많으셨습니다~
안녕하세요.
또 문의드릴거 한 개 남깁니다.
잘 이해가 안되는게 있어서요.
MainContainerViewModel에
public class MainContainerViewModel : BaseViewModel
{`
private RelayCommand<IPropertyOperative> _selectModelCommand;
public ICommand SelectModelCommand { get; set; }
public MainContainerViewModel()
{`
//canExecute는 제가 첨가 했습니다.
//SelectModelCommand = this._selectModelCommand ?? (this._selectModelCommand = new RelayCommand<IPropertyOperative>(this.SelectModel, canExecute));
SelectModelCommand = new RelayCommand<IPropertyOperative>(execute, canExecute);
....
private void execute(IPropertyOperative model)
{
this.PropertyExplorer.SelectModel(model);
}
private bool canExecute(IPropertyOperative arg)
{
//return true;
Debug.WriteLine($"canExcute : {arg}, shape : {this._selectedShape}");
//return true;
if (this._selectedShape != null)
{
Debug.WriteLine("return : true");
return true;
}
else
{
Debug.WriteLine("return : false");
return false;
}
}
이렇게 만들었고요.
MainContainer에
<ListBox.ContextMenu>
<ContextMenu>
<MenuItem Header="Property" Command="{Binding SelectModelCommand}"
CommandParameter="{Binding SelectedShape}"/>
</ContextMenu>
</ListBox.ContextMenu>
이렇게 해줬는데요.
원래 RelayCommand가 canExecute를 계속 검사하다가 조건이 되면 execute하는거로 알고 있거든요.
근데 제가 Canvas를 클릭하면 _selectedShape 이게 null이 되게(정확하게 ListBox.Unselectall()) 코드 비하인드에 넣어놨는데요. _selectedShape 이게 null은 확인했는데요. 문제는 canExecute가 계속 해서 실행되는게 아닌가봐요.
한 값으로 고정되버려요. true든 false든 갱신이 안되는 느낌입니다.
혹시 알 수 있을까요?
원래 RelayCommand가 canExecute를 계속 검사하다가 조건이 되면 execute하는거로 알고 있거든요.
진짜 그런 지 확인을 한 번 해보세요. 알고 있는 게 틀린 것일 수 있잖아요. -ㅁ-;;; (확실하지 않으면 일단 검색과 공부 ㄱㄱ)
요거는 따로 검색을 해보시면 어렵지 않게 알 수 있을텐데요. CanExecute 는 기본적으로 ICommand 인터페이스의 CanExecuteChanged 이벤트가 발생하면 그 때 호출되는 겁니다. (계속 감시하는 게 아니에요.) 그럼 CanExecuteChanged 는 언제 발생하느냐. https://social.msdn.microsoft.com/Forums/sqlserver/en-US/40ec8aeb-6ceb-4ce2-a050-d9309642240c/when-does-wpf-call-the-command-canexecute?forum=wpf 요기 보면 대충 설명되어 있는데 더 자세한 문서는 지금 못 찾겠네요. 대략 view 의 변화가 일어날만한 위치에서 발생한다고 보면 됩니다. 주로 mouse 액션에 의한 포커스 이동 따위가 그렇죠. 그러다 보니 이게 의도한대로 발생하지 않을 수도 있어요. 그래서 특별한 동작에서 강제로 호출할 수도 있지만 일단 여기서는 그 문제가 아닌거 같으니까 패쓰... (https://blog.naver.com/vactorman/80174271876) 요거 한 번 훑어보세욜..
그리고 ContextMenu(Menu, MenuItem , etc) 와 Popup 컨트롤은 좀 특별한 녀석들이에욤. 얘네는 로딩될 때 VisualTree 에 편입되는 게 아니라, 자기네가 렌더링될 때 VisualTree 에 편입되는 애들이어요. 그래서 전체가 로딩되는 것을 전체로 binding 구문을 만들면 동작하지 않을 가능성이 높은 애들이에요.
요것두 한 번 찾아보세염. 얘네한테 binding 하는 방법 찾으면 대충 답 나올거에요. ~ㅂ~
답변 감사드립니다.
일단 안되서 제가 별도로 아래와 같은 Command를 만들었는데요.
public class SelectItemCommand<IPropertyOperative> : ICommand
{
MainContainerViewModel VM; // 아마 여기가 Loosely-Coupled 원칙을 훼손하지 않나싶네요...
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public SelectItemCommand(MainContainerViewModel vm)
{
VM = vm; // 아마 여기가 Loosely-Coupled 원칙을 훼손하지 않나싶네요...
}
public bool CanExecute(object parameter)
{
if (parameter != null)
return true;
else
return false;
}
public void Execute(object parameter)
{
VM.SelectModel((Defines.Interfaces.IPropertyOperative)parameter);
}
}
이렇게 작성해서 해결을 했거든요...
아마 그전에 ViewModel에서 작동안된게 혹시 이것 때문일까요?
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
이 코드가 없어서 ??
보내주신 내용을 다시 공부해보겠습니다.
답변 감사합니다.ㅠㅠ
CanExecute 가 호출이 안 된다는 건가요? 호출은 되는데 오동작한다는 건가요? 개인적으로 굳이 SelectItemCommand 를 추가할 필요를 못느끼겠는데... 기존 RelayCommand 로는 할 수 없는 별도의 동작이 필요한건가요? 상황을 명확하게 정리할 필요가 있어보입니다. (작성하신 코드를 보니 대충 짐작은 가는데 정확히 뭘 하고 싶은 건지, 뭐가 궁금한 건지는 잘 모르겠어요.)
두서가 없이 정리가 되서 질문을 드린것 같습니다.
좀 더 이해하시 쉽게 정리해서 올려보겠습니다.
다시 정리해서 말씀드리면 제가 현재 하고 싶은게,
오늘 작업해본건 총 3가지 입니다.
1, 3번은 제가 위에서 보여드린 public class SelectItemCommand
전문을 다시 보면, SelectItemCommand.cs
public class SelectItemCommand<IPropertyOperative> : ICommand
{
MainContainerViewModel VM;
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public SelectItemCommand(MainContainerViewModel vm)
{
VM = vm;
}
public bool CanExecute(object parameter)
{
if (parameter != null)
return true;
else
return false;
}
public void Execute(object parameter)
{
//IPropertyOperative param = parameter as IPropertyOperative;
VM.SelectModel((Defines.Interfaces.IPropertyOperative)parameter);
}
}
MainContainerViewModel.cs
public class MainContainerViewModel : BaseViewModel
{
...중략...
public SelectItemCommand<IPropertyOperative> SelectModelCommand { get; set; }
/// <summary>
/// ItemControl의 ItemSource로 활용되는 ObservableCollection 변수이다.
/// BaseShapeViewModel의 Get할때, 새로운 인스턴스로 넘긴다.
/// </summary>
public ObservableCollection<BaseShapeViewModel> Items { get; } = new ObservableCollection<BaseShapeViewModel>();
private BaseShapeViewModel _selectedShape;
public BaseShapeViewModel SelectedShape
{
get => _selectedShape;
set
{
this.SetProperty(ref this._selectedShape, value);
Debug.WriteLine($"SelectedShape: {_selectedShape}");
//this.SelectModel(this._selectedShape);
}
}
public PropertyExplorerViewModel PropertyExplorer { get; set; } = new PropertyExplorerViewModel();
/// <summary>
/// 생성자를 통한 초기화
/// 사각형, 타원(원), 선을 초기에 등록한다.
/// </summary>
public MainContainerViewModel()
{
SelectModelCommand = new SelectItemCommand<IPropertyOperative>(this);
Random rand = new Random();
var min = 1;
var max = 600;
var rectModel = new RectModel((double)rand.Next(min, max), (double)rand.Next(min, max));
var ellipseModel = new EllipseModel();
var lineModel = new LineModel((double)rand.Next(min, max), (double)rand.Next(min, max));
this.Items.Add(new RectViewModel(rectModel));
this.Items.Add(new EllipseViewModel(ellipseModel));
this.Items.Add(new LineViewModel(lineModel));
}
/// <summary>
/// Model 선택에 따른 해당 모델에 설정된 속성 정보를
/// Property를 PropertyChange를 위한 목록에 등록하는 과정
/// </summary>
/// <param name="model"></param>
public void SelectModel(IPropertyOperative model)
{
if(model != null)
this.PropertyExplorer.SelectModel(model);
}
}
이렇게 ViewModel에 넣었구요.
MainContainer.xaml
...생략...
<ListBox x:Name ="CanvasListBox"
Background="Transparent"
ItemsSource="{Binding Items}"
SelectionMode="Extended"
SelectedItem="{Binding SelectedShape, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
Margin="5"
>
<ListBox.ContextMenu>
<ContextMenu>
<MenuItem x:Name="Property" Header="Property" Command="{Binding SelectModelCommand}"
CommandParameter="{Binding SelectedShape}"/>
</ContextMenu>
</ListBox.ContextMenu>
...생략...
이렇게 해서 구현했습니다.
이렇게 구현해도 괜찮은건지 여쭤보고 싶었고요.
또 한가지는 기존의 MainContainerViewModel.cs에
this._selectModelCommand ?? (this._selectModelCommand = new RelayCommand<IPropertyOperative>(this.SelectModel));
this._selectModelCommand ?? (this._selectModelCommand = new RelayCommand<IPropertyOperative>(this.SelectModel, canExecute));
로 바꾸고 구현을 해봤습니다. 그런데 다음과 같은 문제가 발생했습니다.
발생문제) 처음 프로그램 실행 후, Canvas위의 오브젝트를 클릭하거나 , 바탕화면을 클릭하는 순간 그 결과가 고정이 되버려서 (null, 혹은 RectViewModel 값) 뭔가 OnPropertyChange같이 갱신이 안되는 현상이 나타났습니다.
해결책) 제가 생각한 해결책은 'SelectItemCommand.cs'를 만드는 것이었습니다.
열심히 정리해봤는데 혹시 이해가 되실까요?
여기에 코드별로 댓글다는 게 어려우니 테스트 해본 것들을 PR 로 보내주시면 한 번 볼게요
다시 변경해서 보내드리겠습니다~
원래는 PanningStructure 역시 관리 대상으로 만들었습니다. 그래서 InCanvasMovingBehavior 에서 AP 로 get/set 하기 위해 DO 를 상속받은 거였는데, 생각해보니 딱히 관리가 필요 없을 거 같아서 InCanvasMovingBehavior 의 AP 를 삭제했고, PanningStruture 의 DO 상속은 제거하는 걸 빠뜨린 겁니다. DO 상속은 삭제해도 됩니다.(물론 그냥 둬도 됩니다.) PanningStruture 가 DO 에 의존적인 기능을 사용하지 않으니까 상관없습니다. 나중에 관리가 필요해 지면 그 때 다시 붙이면 되니까요.
렬루? 오해하시면 안 되는 게, InCavasMovingBehavior 역시 view 의 일부분입니다. 여기서 ViewModel 에 대한 접근은 MVVM 을 깨는 행위예요. 물론 인터페이스를 이용한 느슨한 결합으로 접근할 수도 있지만 엄격한 MVVM 구현 차원에서는 권장하지 않아요. 다시 확인해 보시죵. 진짜 ViewModel 인지.
당연히 가능하겠죠? 구현하는 방법은 많아요. 선택은 만드는 사람이 결정할 문제이지요.
전 개인적으로 엄격한 MVVM 패턴 구현 시 RountedCommand 추천하지 않아요. 구현체가 PresentationCore 에 정의되어 있거든요. RountedCommand 를 CustomControl 등, view 간 연결에서 사용하는 건 상관없지만, 어차피 ViewModel 에서는 사용 못하기 떄문에 쓸 수도 없고 쓰는 걸 권장하지도 않아요. Command 가 왜 필요한 지 이해하면 제 말이 이해가 될 겁니다.
요거는 제가 리뷰에 댓글 달아놓은 걸 확인하시면 될 듯... =ㅂ=
Originally posted by @shwlee in https://github.com/lsirikh/ProperyExplorerTest/issues/7#issuecomment-933157760