簡體   English   中英

WPF用戶控件和名稱范圍

[英]WPF user controls and name scoping

我一直在玩WPF和MVVM,並注意到一件奇怪的事情。 在自定義用戶控件上使用{Binding ElementName=...} ,用戶控件中的根元素的名稱似乎在使用該控件的窗口中可見。 說,這是一個示例用戶控件:

<UserControl x:Class="TryWPF.EmployeeControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:TryWPF"
             Name="root">
  <Grid>
    <Grid.ColumnDefinitions>
      <ColumnDefinition Width="*" />
      <ColumnDefinition Width="Auto" />
    </Grid.ColumnDefinitions>
    <TextBlock Grid.Column="0" Text="{Binding}"/>
    <Button Grid.Column="1" Content="Delete"
                Command="{Binding DeleteEmployee, ElementName=root}"
                CommandParameter="{Binding}"/>
  </Grid>
</UserControl>

看起來非常合法。 現在,依賴屬性DeleteEmployee在代碼隱藏中定義,如下所示:

public partial class EmployeeControl : UserControl
{
    public static DependencyProperty DeleteEmployeeProperty
        = DependencyProperty.Register("DeleteEmployee",
                                      typeof(ICommand),
                                      typeof(EmployeeControl));

    public EmployeeControl()
    {
        InitializeComponent();
    }

    public ICommand DeleteEmployee
    {
        get
        {
            return (ICommand)GetValue(DeleteEmployeeProperty);
        }
        set
        {
            SetValue(DeleteEmployeeProperty, value);
        }
    }
}

這里沒什么神秘的。 然后,使用控件的窗口如下所示:

<Window x:Class="TryWPF.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:TryWPF"
        Name="root"
        Title="Try WPF!" Height="350" Width="525">
  <StackPanel>
    <ListBox ItemsSource="{Binding Employees}" HorizontalContentAlignment="Stretch">
      <ListBox.ItemTemplate>
        <DataTemplate>
          <local:EmployeeControl
            HorizontalAlignment="Stretch"
            DeleteEmployee="{Binding DataContext.DeleteEmployee, ElementName=root}"/>
        </DataTemplate>
      </ListBox.ItemTemplate>
    </ListBox>
  </StackPanel>
</Window>

沒有什么花哨......除了窗口和用戶控件都具有相同名稱的事實! 但是我希望root在整個窗口XAML文件中意味着相同的事情,因此請參考窗口,而不是用戶控件。 唉,運行時會打印以下消息:

System.Windows.Data錯誤:40:BindingExpression路徑錯誤:'object'''String'(HashCode = -843597893)'上找不到'DeleteEmployee'屬性。 BindingExpression:路徑= DataContext.DeleteEmployee; DataItem ='EmployeeControl'(Name ='root'); target元素是'EmployeeControl'(Name ='root'); target屬性是'DeleteEmployee'(類型'ICommand')

DataItem='EmployeeControl' (Name='root')讓我認為它將ElementName=root視為引用控件本身。 它在string上查找DeleteEmployee的事實證實了這種懷疑,因為string正是我設計的VM中的數據上下文。 這是為了完整性:

class ViewModel
{
    public ObservableCollection<string> Employees { get; private set; }
    public ICommand DeleteEmployee { get; private set; }

    public ViewModel()
    {
        Employees = new ObservableCollection<string>();
        Employees.Add("e1");
        Employees.Add("e2");
        Employees.Add("e3");
        DeleteEmployee = new DelegateCommand<string>(OnDeleteEmployee);
    }

    private void OnDeleteEmployee(string employee)
    {
        Employees.Remove(employee);
    }
}

它被實例化並分配給構造函數中的窗口,這是窗口代碼隱藏的唯一內容:

    public MainWindow()
    {
        InitializeComponent();
        DataContext = new ViewModel();
    }

這種現象提示以下問題:

  1. 這是設計的嗎?
  2. 如果是這樣,那么使用自定義控件的人如何知道它在內部使用的名稱?
  3. 如果Name不應該在自定義控件中使用?
  4. 如果是這樣,那么有哪些替代方案呢? 我轉而在FindAncestor模式下使用{RelativeSource} ,這種方法運行正常,但是有更好的方法嗎?
  5. 這是否與數據模板定義自己的名稱相關的事實有關? 如果我只是重命名它,那么它不會阻止我從模板中引用主窗口,因此名稱不會與控件沖突。

在這種情況下,你對wpf名稱范圍如何工作的困惑是不可思議的

您的問題只是您正在對UserControl應用綁定,UserControl是其自己的名稱范圍的“根”(可以這么說)。 UserControls和幾乎所有容器對象都有自己的名稱范圍。 這些范圍不僅包含子元素,還包含包含名稱范圍的對象。 這就是為什么你可以將x:Name="root"到你的窗口並且(在這種情況下除外)從子控件中找到它。 如果你做不到 ,名望望遠鏡將毫無用處。

當你在一個包圍范圍的范圍內對一個名望鏡的根行動時,會出現混亂。 你的假設是父母的名稱范圍優先,但事實並非如此。 Binding在目標對象上調用FindName,在您的情況下是您的用戶控件 (旁注,Binding沒有做插孔,實際調用可以在ElementObjectRef.GetObject找到,但這是Binding委托調用的地方)

在名稱范圍的根目錄上調用FindName時,僅檢查在此范圍內定義的名稱。 不搜索父作用域。 (編輯...從第46行開始,更多地閱讀源http://referencesource.microsoft.com/#PresentationFramework/src/Framework/MS/Internal/Data/ObjectRef.cs,5a01adbbb94284c0我看到算法走了直到找到目標,直到可視樹為止,因此子范圍優先於父范圍)

所有這一切的結果是你獲得了用戶控件實例而不是窗口,就像你希望的那樣。 現在,回答你的個人問題......

這是設計的嗎?

是的。 否則名稱范圍不起作用。

2.如果是這樣,那么使用自定義控件的人如何知道它在內部使用的名稱?

理想情況下,你不會。 就像你永遠不希望有知道的根目錄的名稱TextBox 有趣的是,在嘗試修改控件的外觀時,了解控件中定義的模板名稱通常很重要...

3.如果Name不應該在自定義控件中使用? 如果是這樣,那么有哪些替代方案呢? 我轉而在FindAncestor模式下使用{RelativeSource},這種方法運行正常,但是有更好的方法嗎?

沒有! 沒關系。 用它。 如果您不與其他人共享UserControl,請確保在遇到此特定問題時更改其名稱。 如果你沒有任何問題,整天重復使用相同的名稱,它不會傷害任何東西。

如果您要共享UserControl ,則應該將其重命名為不會與其他人的名稱沖突的內容。 稱之為MuhUserControlTypeName_MuhRoot_Durr或其他東西。

如果是這樣,那么有哪些替代方案呢? 我轉而在FindAncestor模式下使用{RelativeSource},這種方法運行正常,但是有更好的方法嗎?

羅。 只需更改用戶控件的x:Name ,然后繼續。

5.這是否與數據模板定義自己的名稱相關的事實有關? 如果我只是重命名它,那么它不會阻止我從模板中引用主窗口,因此名稱不會與控件沖突。

不,我不相信。 無論如何,我認為沒有任何理由。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM