簡體   English   中英

從ListBox中的數據對象獲取DataTemplate

[英]Get DataTemplate from data object in ListBox

我有一個ListBoxItemTemplate如下所示:

<DataTemplate DataType="local:Column">
    <utils:EditableTextBlock x:Name="editableTextBlock" Text="{Binding Name, Mode=TwoWay}"/>
</DataTemplate>

Column是一個簡單的類,如下所示:

public Column(string name, bool isVisibleInTable)
{
    Name = name;
    IsVisibleInTable = isVisibleInTable;
}

public string Name { get; set; }
public bool IsVisibleInTable { get; set; }

EditableTextBlock是一個UserControl ,雙擊時變為TextBox ,當Lost Focus時變回TextBlock 它還有一個名為IsInEditMode的屬性,默認情況下為false。 如果為true,則顯示TextBox

問題:
ListBox的ItemsSouce是一個ObservableCollection<Column> 我有一個按鈕,可以為集合添加新的Column 但我的問題是我希望IsInEditMode該Button重新添加為EditableTextBlock 但我只能在ViewModel中訪問Column 如何訪問ItemsSource集合中指定ColumnEditableTextBlock

我能想出的唯一解決方案是從Column派生一個類並為其添加一個屬性(例如:name: IsInEditMode )(或者可能是一個包裝類。 這里有一個類似的答案,建議使用包裝類)並綁定到該類DataTemplate中的屬性如下:

<DataTemplate DataType="local:DerivedColumn">
    <utils:EditableTextBlock x:Name="editableTextBlock" Text="{Binding Name, Mode=TwoWay}"
                             IsInEditMode="{Binding IsInEditMode}"/>
</DataTemplate>

但我不想要這個。 我想要一些方法在XAML中執行此操作,而無需派生類和添加不必要的代碼。 (並且還遵守MVVM規則)

如果您有可以向EditableTextBlock用戶控件添加新依賴項屬性的范圍,則可以考慮添加名為StartupInEditMode默認值,默認為false以保留現有行為。

然后, UserControlLoaded處理程序可以確定StartupInEditMode的狀態,以決定如何初始設置IsInEditMode的值。

//..... Added to EditableTextBlock user control
    public bool StartupInEdit
    {
        get { return (bool)GetValue(StartupInEditProperty); }
        set { SetValue(StartupInEditProperty, value); }
    }

    public static readonly DependencyProperty StartupInEditProperty = 
        DependencyProperty.Register("StartupInEdit", typeof(bool), typeof(EditableTextBlock ), new PropertyMetadata(false));

    private void EditableTextBlock_OnLoaded(object sender, RoutedEventArgs e)
    {
        IsInEditMode = StartupInEditMode;
    }

對於已在可視樹中的控件, StartupInEdit的更改值無關緊要,因為它僅在創建時評估一次。 這意味着您可以填充ListBox的集合,其中每個EditableTextBlock不處於編輯模式,然后在開始添加新項目時將StartupInEditmMode模式交換為True 然后每個新的EditableTextBlock控件在編輯模式下啟動。

您可以通過指定DataTemplate來完成此行為切換,其中此新屬性的Binding指向視圖的變量而不是集合項。

    <DataTemplate DataType="local:Column">
      <utils:EditableTextBlock x:Name="editableTextBlock"
            Text="{Binding Name, Mode=TwoWay}" 
            StartupInEditMode="{Binding ANewViewProperty, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"/>
    </DataTemplate>

在此示例中,您需要將一個屬性添加到父Window (或Page或用作視圖的內容的任何內容)中,稱為ANewViewProperty 如果您將綁定更改為{Binding DataContext.ANewViewProperty, RelativeSource={RelativeSource AncestorType={x:Type Window}}}則此值可能是視圖模型的一部分。

這個新屬性( ANewViewProperty )甚至不需要實現INotifyPropertyChanged因為綁定將獲得初始值,因為它創建新的EditableTextBlock控件,如果值稍后更改它無論如何都沒有影響。

在最初加載ListBox ItemSource您可以將ANewViewProperty的值設置為False 當您按下按鈕將新項目添加到列表時,將ANewViewProperty的值設置為True意味着現在將在編輯模式下啟動創建的控件。

更新:C#-only,僅查看替代方案

僅代碼,僅查看的替代方法(類似於user2946329的答案)是掛鈎ListBox.ItemContainerGenerator.ItemsChanged處理程序,該處理程序將在添加新項目時觸發。 觸發后,您現在正在處理新項目(通過布爾DetectingNewItemsDetectingNewItems ),該新項目為新添加的項目找到適當的ListBoxItem可視容器的第一個后代EditableTextBlock控件。 獲得控件的引用后,請更改IsInEditMode屬性。

//.... View/Window Class

    private void MainWindow_OnLoaded(object sender, RoutedEventArgs e)
    {
      MyListBox.ItemContainerGenerator.ItemsChanged += ItemContainerGenerator_ItemsChanged;
    }

    private void ItemContainerGenerator_ItemsChanged(object sender, System.Windows.Controls.Primitives.ItemsChangedEventArgs e)
    {
      if ((e.Action == NotifyCollectionChangedAction.Add) && DetectingNewItems)
      {
        var listboxitem = LB.ItemContainerGenerator.ContainerFromIndex(e.Position.Index + 1) as ListBoxItem;

        var editControl = FindFirstDescendantChildOf<EditableTextBlock>(listboxitem);
        if (editcontrol != null) editcontrol.IsInEditMode = true;
      }
    }

    public static T FindFirstDescendantChildOf<T>(DependencyObject dpObj) where T : DependencyObject
    {
        if (dpObj == null) return null;

        for (var i = 0; i < VisualTreeHelper.GetChildrenCount(dpObj); i++)
        {
            var child = VisualTreeHelper.GetChild(dpObj, i);
            if (child is T) return (T)child;

            var obj = FindFirstChildOf<T>(child);

            if (obj != null) return obj;
        }

        return null;
    }

更新#2(根據評論)

假設您在DataContext保留對View Model的引用,請向視圖添加一個引用ViewModel的屬性: -

    .....  // Add this to the Window/Page

    public bool DetectingNewItems
    {
        get
        {
            var vm = DataContext as MyViewModel;
            if (vm != null)
                return vm.MyPropertyOnVM;
            return false;
        }
    }

    .....  

要在模板中獲取元素並在代碼中更改它的屬性,您需要FrameworkTemplate.FindName Method (String, FrameworkElement)

private childItem FindVisualChild<childItem>(DependencyObject obj)
where childItem : DependencyObject
{
    for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
    {
        DependencyObject child = VisualTreeHelper.GetChild(obj, i);
        if (child != null && child is childItem)
            return (childItem)child;
        else
        {
            childItem childOfChild = FindVisualChild<childItem>(child);
            if (childOfChild != null)
                return childOfChild;
        }
    }
    return null;
}

然后:

for (int i = 0; i < yourListBox.Items.Count; i++)
{
    ListBoxItem yourListBoxItem = (ListBoxItem)(yourListBox.ItemContainerGenerator.ContainerFromIndex(i));
    ContentPresenter contentPresenter = FindVisualChild<ContentPresenter>(yourListBoxItem);
    DataTemplate myDataTemplate = contentPresenter.ContentTemplate;
    EditableTextBlock editable = (EditableTextBlock) myDataTemplate.FindName("editableTextBlock", contentPresenter);
    //Do stuff with EditableTextBlock
    editable.IsInEditMode = true;
}

暫無
暫無

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

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