CanExecute가 처음 호출될 때 WPF CommandParameter는 NULL입니다.
ItemsControl의 DataTemplate 내의 버튼에 바인드된 WPF 및 명령어에 문제가 발생했습니다.시나리오는 아주 간단하다.ItemsControl은 객체 목록에 바인딩되어 있으며 버튼을 클릭하여 목록 내의 각 객체를 삭제할 수 있습니다.버튼은 명령을 실행하고 명령어는 삭제를 처리합니다.CommandParameter는 삭제할 개체에 바인딩되어 있습니다.그래야 사용자가 무엇을 클릭했는지 알 수 있습니다.사용자는 자신의 "소유" 객체만 삭제할 수 있습니다.따라서 명령어의 "CanExecute" 호출에서 몇 가지 검사를 수행하여 사용자에게 올바른 권한이 있는지 확인해야 합니다.
문제는 CanExecute에 전달된 파라미터가 처음 호출되었을 때 NULL이기 때문에 로직을 실행하여 명령어를 활성화/비활성화할 수 없다는 것입니다.단, 항상 활성화한 후 버튼을 클릭하여 명령을 실행하면 CommandParameter가 올바르게 전달됩니다.즉, Command Parameter에 대한 바인딩이 동작하고 있습니다.
ItemsControl 및 DataTemplate의 XAML은 다음과 같습니다.
<ItemsControl
x:Name="commentsList"
ItemsSource="{Binding Path=SharedDataItemPM.Comments}"
Width="Auto" Height="Auto">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Button
Content="Delete"
FontSize="10"
Command="{Binding Path=DataContext.DeleteCommentCommand, ElementName=commentsList}"
CommandParameter="{Binding}" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
보시는 바와 같이 Comments 오브젝트 목록이 있습니다.DeleteCommentCommand의 CommandParameter를 Command 객체에 바인드하고 싶다.
그래서 제 질문은 이런 문제를 경험해 본 사람이 있는가 하는 것입니다.CanExecute는 명령어로 호출되지만 파라미터는 항상 NULL이 됩니다.이유는 무엇입니까?
업데이트: 문제를 조금 좁힐 수 있었습니다.CommandParameter가 데이터 바인딩일 때 메시지를 출력할 수 있도록 빈 Debug Value Converter를 추가했습니다.CanExecute 메서드는 CommandParameter가 버튼에 바인드되기 전에 실행된다는 것이 문제입니다.명령 전에 명령 매개 변수를 설정하려고 했지만(제안된 대로) 여전히 작동하지 않습니다.조종하는 방법에 대한 힌트.
업데이트 2:명령어를 강제로 재평가할 수 있도록 바인딩이 "완료"된 것을 검출할 수 있는 방법이 있습니까?또한 Command-Object의 동일한 인스턴스에 바인딩되어 있는 여러 버튼(ItemsControl의 각 항목마다 하나씩)이 있는 것이 문제입니까?
업데이트 3:버그 복제품을 SkyDrive(http://cid-1a08c11c407c0d8e.skydrive.live.com/self.aspx/Code%20samples/CommandParameterBinding.zip에 업로드했습니다.
뷰 모델에서 명령어에 바인드하려고 할 때도 같은 문제가 발생했습니다.
이름을 사용하여 요소를 참조하는 것이 아니라 상대 소스 바인딩을 사용하도록 변경했고, 그것이 성공했습니다.매개 변수 바인딩이 변경되지 않았습니다.
이전 코드:
Command="{Binding DataContext.MyCommand, ElementName=myWindow}"
새 코드:
Command="{Binding DataContext.MyCommand, RelativeSource={RelativeSource AncestorType=Views:MyView}}"
업데이트: ElementName을 사용하지 않고 이 문제를 발견했습니다. 뷰 모델의 명령에 바인딩되어 있으며 버튼의 데이터 컨텍스트는 뷰 모델입니다.이 경우 (XAML의) 버튼 선언에서 Command Atribute 앞으로 Command Parameter Atribute를 이동하기만 하면 됩니다.
CommandParameter="{Binding Groups}"
Command="{Binding StartCommand}"
Command Parameter와 Command Parameter를 설정하는 순서가 다르다는 것을 알게 되었습니다.Command 속성을 설정하면 CanExecute가 즉시 호출되므로 CommandParameter가 이미 설정되어 있어야 합니다.
XAML의 속성 순서를 바꾸면 실제로 효과가 있다는 것을 알게 되었습니다만, 문제가 해결될지는 잘 모르겠습니다.그래도 시도해 볼 만 하군요.
이 버튼은 절대로 활성화되지 않을 것을 제안하고 있는 것 같습니다.놀랍게도 이 예에서는 Command Parameter가 Command 속성 바로 뒤에 설정됩니다.Command Manager를 호출합니다.Invalidate Requery Suggested()로 인해 버튼이 활성화됩니까?
나는 우연히 비슷한 문제를 발견하고 신뢰할 수 있는 Trigger Converter를 사용하여 해결했습니다.
public class TriggerConverter : IMultiValueConverter
{
#region IMultiValueConverter Members
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
// First value is target value.
// All others are update triggers only.
if (values.Length < 1) return Binding.DoNothing;
return values[0];
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}
이 값 변환기는 임의의 수의 파라미터를 취득하여 이들 중 첫 번째 파라미터를 변환된 값으로 되돌립니다.이 경우 MultiBinding에서 사용하면 다음과 같습니다.
<ItemsControl
x:Name="commentsList"
ItemsSource="{Binding Path=SharedDataItemPM.Comments}"
Width="Auto" Height="Auto">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Button
Content="Delete"
FontSize="10"
CommandParameter="{Binding}">
<Button.Command>
<MultiBinding Converter="{StaticResource TriggerConverter}">
<Binding Path="DataContext.DeleteCommentCommand"
ElementName="commentsList" />
<Binding />
</MultiBinding>
</Button.Command>
</Button>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
이 기능을 사용하려면 어딘가에 Trigger Converter를 리소스로 추가해야 합니다.이제 Command Parameter의 값을 사용할 수 있게 되기 전에 Command 속성이 설정됩니다.Relative Source 에 바인드 할 수도 있습니다.[ Self ]및 [Command Parameter ]를 사용하여 동일한 효과를 얻을 수 있습니다.
이 문제를 해결하기 위해 공유하고자 했던 다른 옵션을 생각해 냈습니다.명령어의 CanExecute 메서드는 CommandParameter 속성이 설정되기 전에 실행되므로 바인딩이 변경되면 강제로 CanExecute 메서드를 다시 호출하는 속성이 연결된 도우미 클래스를 만들었습니다.
public static class ButtonHelper
{
public static DependencyProperty CommandParameterProperty = DependencyProperty.RegisterAttached(
"CommandParameter",
typeof(object),
typeof(ButtonHelper),
new PropertyMetadata(CommandParameter_Changed));
private static void CommandParameter_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var target = d as ButtonBase;
if (target == null)
return;
target.CommandParameter = e.NewValue;
var temp = target.Command;
// Have to set it to null first or CanExecute won't be called.
target.Command = null;
target.Command = temp;
}
public static object GetCommandParameter(ButtonBase target)
{
return target.GetValue(CommandParameterProperty);
}
public static void SetCommandParameter(ButtonBase target, object value)
{
target.SetValue(CommandParameterProperty, value);
}
}
다음으로 명령어 파라미터를 바인드하는 버튼은...
<Button
Content="Press Me"
Command="{Binding}"
helpers:ButtonHelper.CommandParameter="{Binding MyParameter}" />
나는 이것이 다른 누군가가 그 문제에 도움이 되기를 바란다.
DataGridTemplateColumn에 적합한 기능을 버튼으로 추가합니다.
바인딩을 변경합니다.
CommandParameter="{Binding .}"
로.
CommandParameter="{Binding DataContext, RelativeSource={RelativeSource Self}}"
왜 효과가 있는지는 모르겠지만, 난 효과가 있었어.
최근 같은 문제를 발견했습니다(나에게는 컨텍스트메뉴의 메뉴 항목).모든 상황에 적합한 해결책은 아닐 수 있지만, 이 문제를 해결하는 다른 방법(훨씬 짧은 방법!)을 찾았습니다.
<MenuItem Header="Open file" Command="{Binding Tag.CommandOpenFile, IsAsync=True, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}" CommandParameter="{Binding Name}" />
하다.Tag
한 경우에 한 것은 '콘텍스트메뉴'를 입니다.★★★★★★★★★★★★★★★★★★★★★★★·CommandParameter
단, , ,, the, the, the을 묶는다.Command
IsAsync=True
명령어, 그 명령어)의 바인딩이 CanExecute
call이 조금 있기 는 이미 수 .call)로 합니다.이것은, 잠시 동안은, 유효 상태가 잘못되었을지도 모르지만, 제 경우는, 지극히 받아들일 수 있었습니다.
것을 .CommandParameterBehavior
제가 어제 프리즘 포럼에 올린 글이에요이 명령어는 누락된 동작을 추가합니다.CommandParameter
을 일으키다Command
쿼리됩됩니니다
좀요. 제가 메모리 누수를 한 결과, 당신이 했을 때 를 피하려고 한 PropertyDescriptor.AddValueChanged
에 ""를 호출하지 않고PropertyDescriptor.RemoveValueChanged
. 때 을 해제함으로써 ekement가 언로드 되었을 때 핸들러의 등록을 해제함으로써 수정하려고 합니다.
이, 마, 마, 마, 마, 아, 아, you, you, you, you, you,IDelegateCommand
(Prism 라이브러리를 저와 동일하게 변경하고자 하는 경우)를 사용하지 않는 한 더 많은 정보를 얻을 수 있습니다. 우리는 ,, ,, ,, 음, 음, 음, 음, 음, 、 음 、 음 、 음 、 음 、 음 、 음 、 음 。RoutedCommand
기prprprprpr prism prism prism prism prism prism prism prism prism prism prism prism prism)DelegateCommand<T>
의 경우)로.CommandManager.InvalidateRequerySuggested
양자 파동 붕괴를 일으켜 알려진 우주나 다른 것을 파괴하는 거죠
using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Input;
namespace Microsoft.Practices.Composite.Wpf.Commands
{
/// <summary>
/// This class provides an attached property that, when set to true, will cause changes to the element's CommandParameter to
/// trigger the CanExecute handler to be called on the Command.
/// </summary>
public static class CommandParameterBehavior
{
/// <summary>
/// Identifies the IsCommandRequeriedOnChange attached property
/// </summary>
/// <remarks>
/// When a control has the <see cref="IsCommandRequeriedOnChangeProperty" />
/// attached property set to true, then any change to it's
/// <see cref="System.Windows.Controls.Primitives.ButtonBase.CommandParameter" /> property will cause the state of
/// the command attached to it's <see cref="System.Windows.Controls.Primitives.ButtonBase.Command" /> property to
/// be reevaluated.
/// </remarks>
public static readonly DependencyProperty IsCommandRequeriedOnChangeProperty =
DependencyProperty.RegisterAttached("IsCommandRequeriedOnChange",
typeof(bool),
typeof(CommandParameterBehavior),
new UIPropertyMetadata(false, new PropertyChangedCallback(OnIsCommandRequeriedOnChangeChanged)));
/// <summary>
/// Gets the value for the <see cref="IsCommandRequeriedOnChangeProperty"/> attached property.
/// </summary>
/// <param name="target">The object to adapt.</param>
/// <returns>Whether the update on change behavior is enabled.</returns>
public static bool GetIsCommandRequeriedOnChange(DependencyObject target)
{
return (bool)target.GetValue(IsCommandRequeriedOnChangeProperty);
}
/// <summary>
/// Sets the <see cref="IsCommandRequeriedOnChangeProperty"/> attached property.
/// </summary>
/// <param name="target">The object to adapt. This is typically a <see cref="System.Windows.Controls.Primitives.ButtonBase" />,
/// <see cref="System.Windows.Controls.MenuItem" /> or <see cref="System.Windows.Documents.Hyperlink" /></param>
/// <param name="value">Whether the update behaviour should be enabled.</param>
public static void SetIsCommandRequeriedOnChange(DependencyObject target, bool value)
{
target.SetValue(IsCommandRequeriedOnChangeProperty, value);
}
private static void OnIsCommandRequeriedOnChangeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (!(d is ICommandSource))
return;
if (!(d is FrameworkElement || d is FrameworkContentElement))
return;
if ((bool)e.NewValue)
{
HookCommandParameterChanged(d);
}
else
{
UnhookCommandParameterChanged(d);
}
UpdateCommandState(d);
}
private static PropertyDescriptor GetCommandParameterPropertyDescriptor(object source)
{
return TypeDescriptor.GetProperties(source.GetType())["CommandParameter"];
}
private static void HookCommandParameterChanged(object source)
{
var propertyDescriptor = GetCommandParameterPropertyDescriptor(source);
propertyDescriptor.AddValueChanged(source, OnCommandParameterChanged);
// N.B. Using PropertyDescriptor.AddValueChanged will cause "source" to never be garbage collected,
// so we need to hook the Unloaded event and call RemoveValueChanged there.
HookUnloaded(source);
}
private static void UnhookCommandParameterChanged(object source)
{
var propertyDescriptor = GetCommandParameterPropertyDescriptor(source);
propertyDescriptor.RemoveValueChanged(source, OnCommandParameterChanged);
UnhookUnloaded(source);
}
private static void HookUnloaded(object source)
{
var fe = source as FrameworkElement;
if (fe != null)
{
fe.Unloaded += OnUnloaded;
}
var fce = source as FrameworkContentElement;
if (fce != null)
{
fce.Unloaded += OnUnloaded;
}
}
private static void UnhookUnloaded(object source)
{
var fe = source as FrameworkElement;
if (fe != null)
{
fe.Unloaded -= OnUnloaded;
}
var fce = source as FrameworkContentElement;
if (fce != null)
{
fce.Unloaded -= OnUnloaded;
}
}
static void OnUnloaded(object sender, RoutedEventArgs e)
{
UnhookCommandParameterChanged(sender);
}
static void OnCommandParameterChanged(object sender, EventArgs ea)
{
UpdateCommandState(sender);
}
private static void UpdateCommandState(object target)
{
var commandSource = target as ICommandSource;
if (commandSource == null)
return;
var rc = commandSource.Command as RoutedCommand;
if (rc != null)
{
CommandManager.InvalidateRequerySuggested();
}
var dc = commandSource.Command as IDelegateCommand;
if (dc != null)
{
dc.RaiseCanExecuteChanged();
}
}
}
}
DelegateCommand를 사용하여 이 문제를 "수정"하는 비교적 간단한 방법이 있지만, DelegateCommand 소스를 업데이트하고 Microsoft를 다시 컴파일해야 합니다.연습.컴포지트presentation.dll.
1) Prism 1.2 소스 코드를 다운로드하여 Composite ApplicationLibrary_Desktop.sln을 엽니다.이 안에 컴포지트가 있어요발표.DelegateCommand 소스가 포함된 데스크톱 프로젝트입니다.
2) 공개 이벤트인 EventHandler CanExecuteChanged에서 다음과 같이 변경합니다.
public event EventHandler CanExecuteChanged
{
add
{
WeakEventHandlerManager.AddWeakReferenceHandler( ref _canExecuteChangedHandlers, value, 2 );
// add this line
CommandManager.RequerySuggested += value;
}
remove
{
WeakEventHandlerManager.RemoveWeakReferenceHandler( _canExecuteChangedHandlers, value );
// add this line
CommandManager.RequerySuggested -= value;
}
}
3) 보호된 가상 무효 OnCan Execute Changed()에서 다음과 같이 변경합니다.
protected virtual void OnCanExecuteChanged()
{
// add this line
CommandManager.InvalidateRequerySuggested();
WeakEventHandlerManager.CallWeakReferenceHandlers( this, _canExecuteChangedHandlers );
}
4) 솔루션을 다시 컴파일하여 컴파일된 DLL이 있는 [Debug]폴더 또는 [Release]폴더로 이동합니다.Microsoft 를 카피합니다.연습.컴포지트Presentation.dll 및 .pdb(필요한 경우)를 외부 어셈블리를 참조하는 위치로 이동한 후 응용 프로그램을 다시 컴파일하여 새 버전을 가져옵니다.
이후 UI가 해당 DelegateCommand에 바인딩된 요소를 렌더링할 때마다 CanExecute가 실행됩니다.
잘 있어, 조
지메일로 심판을 보다
유사한 질문에 대한 몇 가지 좋은 답변을 읽은 후, DelegateCommand를 작동시키기 위해 예제를 약간 변경했습니다.사용하는 대신:
public event EventHandler CanExecuteChanged;
다음으로 변경했습니다.
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
다음 두 가지 방법을 수정하는 것이 귀찮아서 제거했습니다.
public void RaiseCanExecuteChanged()
그리고.
protected virtual void OnCanExecuteChanged()
그리고 그게 다야...이것은 바인딩이 변경되었을 때와 실행 메서드 후에 CanExecute가 호출되는 것을 보증하는 것 같습니다.
ViewModel이 변경되어도 자동으로 트리거되지 않지만 이 스레드에서 설명한 바와 같이 CommandManager를 호출하여 트리거할 수 있습니다.GUI 스레드에서 InvalidateRequerySupported가 제안됨
Application.Current?.Dispatcher.Invoke(DispatcherPriority.Normal, (Action)CommandManager.InvalidateRequerySuggested);
이것은 .NET 7.0 RC1에서 수정되었습니다.
(적어도 어떤 의미에서는...)
됩니다.CanExecute()
CommandParameter
초초초초초 ( )
그렇다고 해서 에 대한 첫 번째 콜이 방해되는 것은 아닙니다.CanExecute()
때CommandParameter
null
, 많은, 많은ICommand
실장에서는 이미 그것을 처리하고 있을 필요가 있습니다.또, 회피책/핵을 주문하는 불명확하고 문제가 있는 XAML Atribute가 불필요하게 됩니다.
GitHub 코멘트에서 @Daniel-Svenson이 암시한 바와 같이:
여기서의 진짜 문제는 ICOMMAND입니다.CanExecute는 CommandParameter에 바인딩된 값이 변경되어도 재평가되지 않습니다.명령어 파라미터가 CanExecute에 전달되므로 모든 사람이 직관적으로 이 동작을 예상하기 때문에 이 동작을 실행하는 것이 올바른 동작입니다.
그리고 그것이 고쳐지고 있는 것이다.
@pchaurasia14에 따르면 Sr.Microsoft의 WPF용 엔지니어링 매니저:
이 문제는 RC1 릴리즈에서 수정되었습니다.시험해 보세요.그러니까...NET 7 RC1.
dotnet/wpf 프로젝트의 GitHub 추적 문제 #316은 닫힘으로 표시됩니다.코드 change Command Parameter invalidate CanExecute #4217 은 에 포함되어 있습니다.NET 7.0 RC12022년 7월 21일에 병합되었으며 RC1 릴리즈의 커밋 목록(스크롤 다운)에 포함되어 있습니다.
이것은 WPF에 대한 버그로 에 기록되어 있습니다.Net 4.0(베타 2에서 아직 문제가 발생).
https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=504976
이러한 답변 중 일부는 명령어를 얻기 위해 DataContext에 바인딩하는 것에 관한 것이지만, 문제는 명령어 파라미터가 null이어야 할 때 null이 되어야 한다는 것이었습니다.우리도 이런 경험을 했어요.직감적으로 View Model에서 이 기능을 사용할 수 있는 매우 간단한 방법을 찾았습니다.이는 고객이 보고한 Command Parameter의 특수한 문제에 대한 것입니다.코드의 행은 1개입니다.디스패처를 확인합니다.BeginInvoke().
public DelegateCommand<objectToBePassed> CommandShowReport
{
get
{
// create the command, or pass what is already created.
var command = _commandShowReport ?? (_commandShowReport = new DelegateCommand<object>(OnCommandShowReport, OnCanCommandShowReport));
// For the item template, the OnCanCommand will first pass in null. This will tell the command to re-pass the command param to validate if it can execute.
Dispatcher.BeginInvoke((Action) delegate { command.RaiseCanExecuteChanged(); }, DispatcherPriority.DataBind);
return command;
}
}
이것이 데이터 템플릿에서 동작할지는 모르겠지만 ListView Context 메뉴에서 현재 항목을 명령 파라미터로 가져오기 위해 사용하는 바인딩 구문은 다음과 같습니다.
CommandParameter=
"{Binding RelativeSource={RelativeSource AncestorType=ContextMenu},
Path=PlacementTarget.SelectedItem,
Mode=TwoWay}"
가능성이 희박합니다.이것을 디버깅 하려면 , 다음의 조작을 실행할 수 있습니다.
- Preview Can Execute 이벤트를 확인합니다.
- snoop/wpf mole을 사용하여 내부를 엿보고 명령어 파라미터가 무엇인지 확인합니다.
HTH,
commandManagerInvalidate Requery Suggested는 나에게도 유효합니다.다음 링크에서도 비슷한 문제가 있다고 생각되며, M$dev는 현재 버전의 제한을 확인하였고, commandManager도 확인하였습니다.회피책은 InvalidateRequerySupped입니다.http://social.expression.microsoft.com/Forums/en-US/wpf/thread/c45d2272-e8ba-4219-bb41-1e5eaed08a1f/
중요한 것은 commandManager를 호출하는 타이밍입니다.Invalidate Requery Suggested.이 값은 관련 값 변경이 통지된 후에 호출해야 합니다.
Command Parameter 설정에 대한 Ed Ball의 제안 외에 CanExecute 메서드에 객체유형의 파라미터가 있는지 확인합니다.
private bool OnDeleteSelectedItemsCanExecute(object SelectedItems)
{
// Your goes heres
}
Selected를 받는 방법을 알아내는 데 많은 시간을 할애하지 않기를 바랍니다.CanExecute 매개 변수로서의 항목
언급URL : https://stackoverflow.com/questions/335849/wpf-commandparameter-is-null-first-time-canexecute-is-called
'programing' 카테고리의 다른 글
" " " 를 할 때 합니다." " " 를 할 때 합니다." " " 를 할 때 합니다. (0) | 2023.04.23 |
---|---|
ASP에서 비동기 컨트롤러를 사용해야 하는 경우NET MVC? (0) | 2023.04.23 |
열 머리글 클릭 시 WPF List View/Grid View를 정렬하는 가장 좋은 방법? (0) | 2023.04.23 |
@try - 목표 C의 캐치 블록 (0) | 2023.04.23 |
SQL에서 열을 난수로 채우려면 어떻게 해야 합니까?모든 행에서 동일한 값을 얻을 수 있습니다. (0) | 2023.04.23 |