سؤال

أواجه مشكلة عند ربط أمر في قائمة السياق على عنصر تحكم المستخدم الموجود على صفحة علامة التبويب.في المرة الأولى التي أستخدم فيها القائمة (انقر بزر الماوس الأيمن على علامة التبويب) تعمل بشكل رائع، ولكن إذا قمت بتبديل علامة التبويب، فسيستخدم الأمر مثيل قاعدة البيانات الذي تم استخدامه في المرة الأولى.

إذا قمت بوضع زر مرتبط بالأمر في usercontrol فإنه يعمل كما هو متوقع ...

هل يمكن لأحد أن يخبرني ما الذي أفعله الخطأ؟؟

هذا مشروع اختباري يكشف المشكلة:

التطبيق.xaml.cs:

public partial class App : Application
{
    protected override void OnStartup(StartupEventArgs e)
    {
        base.OnStartup(e);

        CompanyViewModel model = new CompanyViewModel();
        Window1 window = new Window1();
        window.DataContext = model;
        window.Show();
    }
}

Window1.xaml:

<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:vw="clr-namespace:WpfApplication1"
Title="Window1" Height="300" Width="300">

  <Window.Resources>
    <DataTemplate x:Key="HeaderTemplate">
        <StackPanel Orientation="Horizontal">
            <TextBlock Text="{Binding Path=Name}" />
        </StackPanel>
    </DataTemplate>
    <DataTemplate DataType="{x:Type vw:PersonViewModel}">
        <vw:UserControl1/>
    </DataTemplate>

</Window.Resources>
<Grid>
    <TabControl ItemsSource="{Binding Path=Persons}" 
                ItemTemplate="{StaticResource HeaderTemplate}"
                IsSynchronizedWithCurrentItem="True" />
</Grid>
</Window>

UserControl1.xaml:

<UserControl x:Class="WpfApplication1.UserControl1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    MinWidth="200">
    <UserControl.ContextMenu>
        <ContextMenu >
            <MenuItem Header="Change" Command="{Binding Path=ChangeCommand}"/>
        </ContextMenu>
    </UserControl.ContextMenu>
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="100" />
            <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>
        <Label Grid.Column="0">The name:</Label>
        <TextBox Grid.Column="1" Text="{Binding Path=Name, UpdateSourceTrigger=PropertyChanged}" />
    </Grid>
</UserControl>

CompanyViewModel.cs:

public class CompanyViewModel
{
    public ObservableCollection<PersonViewModel> Persons { get; set; }
    public CompanyViewModel()
    {
        Persons = new ObservableCollection<PersonViewModel>();
        Persons.Add(new PersonViewModel(new Person { Name = "Kalle" }));
        Persons.Add(new PersonViewModel(new Person { Name = "Nisse" }));
        Persons.Add(new PersonViewModel(new Person { Name = "Jocke" }));
    }
}

بيرسونفيوموديل.cs:

public class PersonViewModel : INotifyPropertyChanged
{
    Person _person;
    TestCommand _testCommand;

    public PersonViewModel(Person person)
    {
        _person = person;
        _testCommand = new TestCommand(this);
    }
    public ICommand ChangeCommand 
    {
        get
        {
            return _testCommand;
        }
    }
    public string Name 
    {
        get
        {
            return _person.Name;
        }
        set
        {
            if (value == _person.Name)
                return;
            _person.Name = value;
            OnPropertyChanged("Name");
        }
    }
    void OnPropertyChanged(string propertyName)
    {
        PropertyChangedEventHandler handler = this.PropertyChanged;
        if (handler != null)
        {
            var e = new PropertyChangedEventArgs(propertyName);
            handler(this, e);
        }
    }
    public event PropertyChangedEventHandler PropertyChanged;
}

اختبار الأوامر.cs:

public class TestCommand : ICommand
{
    PersonViewModel _person;
    public event EventHandler CanExecuteChanged;

    public TestCommand(PersonViewModel person)
    {
        _person = person;
    }
    public bool CanExecute(object parameter)
    {
        return true;
    }
    public void Execute(object parameter)
    {
        _person.Name = "Changed by command";
    }
}

شخص.cs:

public class Person
{
    public string Name { get; set; }
}
هل كانت مفيدة؟

المحلول

الشيء الرئيسي الذي يجب تذكره هنا هو قوائم السياق ليست جزءًا من الشجرة المرئية.

لذلك، لا يرثون نفس مصدر عنصر التحكم الذي ينتمون إليه للربط.طريقة التعامل مع هذا هي الارتباط بهدف الموضع الخاص بـ contextMenu نفسها.

<MenuItem Header="Change" Command="{Binding 
    Path=PlacementTarget.ChangeCommand, 
    RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContextMenu}}}"
/>

نصائح أخرى

وأنظف طريقة لقد وجدت لربط الأوامر إلى عناصر القائمة السياق ينطوي على استخدام فئة تسمى CommandReference. يمكنك العثور عليها في أدوات MVVM على كود بلاكس على WPF الآجلة .

ووXAML قد تبدو مثل هذا:

<UserControl x:Class="View.MyView" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                xmlns:vm="clr-namespace:ViewModel;assembly=MyViewModel"
                xmlns:mvvm="clr-namespace:ViewModelHelper;assembly=ViewModelHelper"
           <UserControl.Resources>
                <mvvm:CommandReference x:Key="MyCustomCommandReference" Command="{Binding MyCustomCommand}" />

                <ContextMenu x:Key="ItemContextMenu">
                    <MenuItem Header="Plate">
                        <MenuItem Header="Inspect Now" Command="{StaticResource MyCustomCommandReference}"
                                CommandParameter="{Binding}">
                        </MenuItem>
                    </MenuItem>
               </ContextMenu>
    </UserControl.Resources>

وMyCustomCommand هو RelayCommand على ViewModel. في هذا المثال، تم إرفاق ViewModel إلى datacontext وجهة النظر في التعليمات البرمجية.

ملحوظة: تم نسخ هذه XAML من مشروع العمل وتبسيط للتوضيح. قد يكون هناك الأخطاء المطبعية أو الأخطاء الطفيفة الأخرى.

وكان لي نفس المشكلة مؤخرا مع ContextMenu تقع في مربع القائمة. حاولت ربط الأمر طريقة MVVM دون أي رمز وراء. أنا استسلمت في النهاية وسألت صديقا لمساعدته. وجد الحل ملتوية قليلا ولكن موجزة. هو تمرير مربع القائمة في DataContext من ContextMenu ومن ثم العثور على الأمر في نموذج عرض عن طريق الوصول إلى DataContext من مربع القائمة. هذا هو أبسط الحلول التي رأيتها حتى الآن. أي رمز مخصص، لا الوسم، فقط XAML نقية وMVVM.

ونشرت لي عينة العمل بشكل كامل على جيثب . هنا مقتطف من XAML.

<Window x:Class="WpfListContextMenu.MainWindow" 
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        Title="MainWindow" Height="350" Width="268">
  <Grid>
    <DockPanel>
      <ListBox x:Name="listBox" DockPanel.Dock="Top" ItemsSource="{Binding Items}" DisplayMemberPath="Name"
               SelectionMode="Extended">
        <ListBox.ContextMenu>
          <ContextMenu DataContext="{Binding Path=PlacementTarget, RelativeSource={RelativeSource Self}}">
            <MenuItem Header="Show Selected" Command="{Binding Path=DataContext.ShowSelectedCommand}"
                      CommandParameter="{Binding Path=SelectedItems}" />
          </ContextMenu>
        </ListBox.ContextMenu>
      </ListBox>
    </DockPanel>
  </Grid>
</Window>

وأنا أفضل حل آخر. إضافة سياق الحدث محمل القائمة.

<ContextMenu Loaded="ContextMenu_Loaded"> 
    <MenuItem Header="Change" Command="{Binding Path=ChangeCommand}"/> 
</ContextMenu> 

وتعيين السياق البيانات داخل الحدث.

private void ContextMenu_Loaded(object sender, RoutedEventArgs e)
{
    (sender as ContextMenu).DataContext = this; //assignment can be replaced with desired data context
}

ولقد وجدت هذه الطريقة باستخدام خاصية العلامات مفيدة جدا عند الربط من قائمة سياق عمق قالب التحكم:

HTTP: //blog.jtango. صافي /-ملزم ل-MENUITEM-في-برنامج الأغذية العالمي-السياق القائمة

وهذا يجعل من الممكن ربط أي datacontext المتاحة لعنصر التحكم الذي افتتح قائمة السياق من بينها. قائمة السياق يمكن الوصول إلى السيطرة من خلال النقر على "PlacementTarget". إذا لا بد من خاصية العلامات من السيطرة النقر عليها إلى datacontext المطلوب، ملزمة ب "PlacementTarget.Tag" من داخل قائمة السياق سوف مقلاع بك مباشرة إلى أن datacontext.

وأنا أعلم أن هذه هي بالفعل آخر العمر، ولكنني أود أن أضيف حل آخر لأولئك الذي يبحثون عن طرق مختلفة للقيام بذلك.

وأنا لا يمكن أن تجعل نفس الحل للعمل في حالتي، منذ كنت أحاول أن أفعل شيئا آخر: فتح قائمة السياق بنقرة ماوس (تماما مثل شريط الأدوات مع قائمة فرعية ملحقة به)، وكذلك ربط الأوامر ل طرازى. منذ كنت تستخدم الزناد الحدث، وكان الهدف PlacementTarget فارغة.

وهذا هو الحل وجدت والعمل على انجاحه فقط باستخدام XAML:

<!-- This is an example with a button, but could be other control -->
<Button>
  <...>

  <!-- This opens the context menu and binds the data context to it -->
  <Button.Triggers>
    <EventTrigger RoutedEvent="Button.Click">
      <EventTrigger.Actions>
        <BeginStoryboard>
          <Storyboard>
            <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="ContextMenu.DataContext">
              <DiscreteObjectKeyFrame KeyTime="0:0:0" Value="{Binding}"/>
            </ObjectAnimationUsingKeyFrames>
            <BooleanAnimationUsingKeyFrames Storyboard.TargetProperty="ContextMenu.IsOpen">
              <DiscreteBooleanKeyFrame KeyTime="0:0:0" Value="True"/>
            </BooleanAnimationUsingKeyFrames>
          </Storyboard>
        </BeginStoryboard>
      </EventTrigger.Actions>
    </EventTrigger>
  </Button.Triggers>

  <!-- Here it goes the context menu -->
  <Button.ContextMenu>
    <ContextMenu>
      <MenuItem Header="Item 1" Command="{Binding MyCommand1}"/>
      <MenuItem Header="Item 2" Command="{Binding MyCommand2}"/>
    </ContextMenu>
  </Button.ContextMenu>

</Button>
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top