簡體   English   中英

我如何確保在其他任何事情之前調用 onApplyTemplate

[英]How do I ensure that onApplyTemplate gets called before anything else

我有一個 wpf 自定義控件,我一直在使用它。 它有一個共享的 New,如下所示:

Shared Sub New()
    'This OverrideMetadata call tells the system that this element wants to provide a style that is different than its base class.
    'This style is defined in themes\generic.xaml
    DefaultStyleKeyProperty.OverrideMetadata(GetType(VtlDataNavigator_24), New FrameworkPropertyMetadata(GetType(VtlDataNavigator_24)))
    ItemsSourceProperty.OverrideMetadata(GetType(VtlDataNavigator_24), New FrameworkPropertyMetadata(Nothing, AddressOf OnItemsSourceHasChanged))
End Sub

如果已為自定義控件設置了 Items 源,則此共享子然后調用 itemssource 的 overrideMetadata(如下所示)

  Private Shared Sub OnItemsSourceHasChanged(ByVal d As DependencyObject, ByVal baseValue As Object)
    Dim vdn As VtlDataNavigator_24 = DirectCast(d, VtlDataNavigator_24)
    vdn.RecordCount = vdn.Items.SourceCollection.Cast(Of Object)().Count()
    vdn.MyBaseCollection = DirectCast(vdn.ItemsSource, ICollectionView)
    vdn.MyBaseEditableCollection = DirectCast(vdn.ItemsSource, IEditableCollectionView)
    vdn.MyBaseCollection.MoveCurrentToFirst
    vdn.RecordIndex = vdn.MyBaseCollection.CurrentPosition + 1
    If Not IsNothing(vdn.FindButton) Then
        If vdn.FindButton.Visibility = Visibility.Visible Then
            vdn.RecordIndexTextBox.IsReadOnly = False
        Else
            vdn.RecordIndexTextBox.IsReadOnly = True
        End If
    End If
    vdn.ResetTheNavigationButtons
    vdn.SetupInitialStatesForNonNavigationButtons
End Sub

這將失敗,因為代碼中引用的按鈕(以及從中調用的例程)尚未實例化,因為尚未調用 OnApplyTemplate(如下所示)的覆蓋。

Public Overrides Sub OnApplyTemplate()
    MyBase.OnApplyTemplate()
    RecordIndexTextBox = CType(GetTemplateChild("PART_RecordIndexTextBox"), TextBox)
    RecordCountTextBox = CType(GetTemplateChild(RecordCountTextBoxPart), TextBox)
    RecordTextBlock = CType(GetTemplateChild(RecordTextBlockPart), TextBlock)
    OfTextBlock = CType(GetTemplateChild(OfTextBlockPart), TextBlock)
    FirstButton = CType(GetTemplateChild(FirstButtonPart), Button)
    PreviousButton = CType(GetTemplateChild(PreviousButtonPart), RepeatButton)
    NextButton = CType(GetTemplateChild(NextButtonPart), RepeatButton)
    LastButton = CType(GetTemplateChild(LastButtonPart), Button)
    AddButton = CType(GetTemplateChild(AddButtonPart), Button)
    CancelNewRecordButton = CType(GetTemplateChild(CancelNewButtonPart), Button)
    EditButton = CType(GetTemplateChild(EditButtonPart), button)
    CancelButton = CType(GetTemplateChild(CancelButtonPart), Button)
    RefreshButton = CType(GetTemplateChild(RefreshButtonPart), Button)
    SaveButton = CType(GetTemplateChild(SaveButtonPart), Button)
    DeleteButton = CType(GetTemplateChild(DeleteButtonPart), Button)
    FindButton = CType(GetTemplateChild(FindButtonPart), Button)
End Sub

如果我添加以下內容:

vdn.OnApplyTemplate

到 OnItemsSourceHasChanged,OnApplyTemplate 被調用但沒有解決任何問題(見下圖)。

在此處輸入圖片說明

但是如果我沒有在我的控件上設置一個 itemssource,然后 OnApplyTemplate 被調用並且項目解析(見下文)

在此處輸入圖片說明

有沒有人以前遇到過這種行為並找到了一種方法來糾正它,這樣 OnApplyTemplate 總是在任何可能需要訪問尚未解決的控件之前被調用的第一件事。

編輯

關於這個問題的奇怪之處在於(這似乎並不總是如此!)這一直有效,直到顯然我做了一些事情或設置了一些屬性。 我剩下的是一個項目,如果我沒有在我的自定義控件上設置項目源,則該項目會運行,如果我這樣做,則不會運行,因為當項目源在我的自定義控件在 OnApplyTemplate 被調用之前運行。

好吧,我終於能夠確定在繪制和呈現控件之前我的自定義控件 Itemssource 屬性正在更改,因此我在 ItemsSource 更改后進行設置的代碼引發了空引用異常,因為主控件尚未呈現。

鑒於它確實有效,它一定是我做過的事情,但我現在對如何進一步深入研究並真正找到原因沒有想法。 我歡迎您提出任何建議或潛在的工作輪次。

根據以下評論進行編輯:控制模板的典型部分。

<!-- First Button -->

                        <Button Style="{StaticResource vtlNavButtonStyle}"
                                x:Name="PART_FirstButton"
                                Tag="First_Button"
                                Visibility="{Binding Path=NavigationButtonVisibility,Converter={StaticResource booltovis}, RelativeSource={RelativeSource TemplatedParent}}"
                                ToolTipService.ShowOnDisabled="False"
                                ToolTipService.ShowDuration="3000"
                                ToolTipService.InitialShowDelay="500">
                            <Button.ToolTip>
                                <Binding Path="FirstButtonToolTip"
                                         RelativeSource="{RelativeSource TemplatedParent}"
                                         TargetNullValue="{x:Static p:Resources.FirstText}">
                                </Binding>
                            </Button.ToolTip>
                            <StackPanel>
                                <Image Style="{StaticResource vtlImageStyle}">
                                    <Image.Source>
                                        <Binding Path="FirstImage"
                                                 RelativeSource="{RelativeSource TemplatedParent}">
                                            <Binding.TargetNullValue>
                                                <ImageSource>/VtlWpfControls;component/Images/16/first.png</ImageSource>
                                            </Binding.TargetNullValue>
                                        </Binding>
                                    </Image.Source>
                                </Image>
                            </StackPanel>
                        </Button>

自己調用 OnApplyTemplate 不會有幫助; 框架將在實際應用模板時調用它。 也就是說,事情發生的順序不是確定性的——在設置ItemsSource之前可能會或可能不會應用模板。 我正在使用適用於 Windows 10 的 UWP 應用程序,這是一個稍微不同的野獸,但我們已經解決了類似的問題,執行如下操作:

private TextBlock textBlock;

protected override void OnApplyTemplate()
{
    base.OnApplyTemplate();

    // Grab the template controls, e.g.:
    textBlock = GetTemplateChild("MyTextBlock") as TextBlock;

    InitializeDataContext();
    DataContextChanged += (sender, args) => InitializeDataContext();
}

private void InitializeDataContext()
{
    ViewModel ViewModel = DataContext as ViewModel;
    if (viewModel != null)
    {
        // Here we know that both conditions are satisfied
        textBlock.Text = ViewModel.Name;
    }
}

關鍵是在應用模板之前不要開始偵聽DataContextChanged 如果已經設置了數據上下文,則第一次調用initializeDataContext會處理事情; 如果沒有,回調會處理事情。

(在你的情況下,我想用項目源監聽替換我們的數據上下文監聽。)

這不是您問題的答案,而是擴展了您在評論中提到的一些內容。

我真的認為查看 WPF 命令對您有益,因為它們與自定義控件有關。 您的數據導航器控件聽起來基本上支持您使用控件模板中的Button控件調用的許多操作(轉到第一個/上一個/下一個/最后一個;添加;編輯;取消;等等)。 與其在OnApplyTemplate查找按鈕(此時您存儲對它們的引用,以便您以后可能會掛鈎到它們的Click事件),您應該支持控件中的命令:模板中的按鈕隨后將綁定到這些命令。

一個例子可能會使這更清楚一點。 以下是支持兩種操作的自定義控件的代碼:轉到第一頁和轉到最后一頁。 在靜態構造函數中,我注冊了兩個命令綁定,每個操作一個。 這些工作通過調用一個輔助方法來“綁定”到命令,加上一對在調用操作時被調用的委托。

我在這里使用的命令由 WPF 框架提供,並且是包含在靜態NavigationCommands類中的靜態屬性。 (還有很多其他類似的類包含命令,只需點擊該 MSDN 頁面“另請參閱”部分中的鏈接即可)。

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;

namespace StackOverflow
{
    public class TestControl : Control
    {
        static TestControl()
        {
            RegisterCommandBinding<TestControl>(NavigationCommands.FirstPage,
                x => x.GoToFirstPage());

            RegisterCommandBinding<TestControl>(NavigationCommands.LastPage,
                x => x.GoToLastPage(), x => x.CanGoToLastPage());

            DefaultStyleKeyProperty.OverrideMetadata(typeof(TestControl),
                new FrameworkPropertyMetadata(typeof(TestControl)));
        }

        void GoToFirstPage()
        {
            Console.WriteLine("first page");
        }

        void GoToLastPage()
        {
            Console.WriteLine("last page");
        }

        bool CanGoToLastPage()
        {
            return true;  // Would put your own logic here obviously
        }

        public static void RegisterCommandBinding<TControl>(
            ICommand command, Action<TControl> execute) where TControl : class
        {
            RegisterCommandBinding<TControl>(command, execute, target => true);
        }

        public static void RegisterCommandBinding<TControl>(
            ICommand command, Action<TControl> execute, Func<TControl, bool> canExecute)
            where TControl : class
        {
            var commandBinding = new CommandBinding(command,
                (target, e) => execute((TControl) target),
                (target, e) => e.CanExecute = canExecute((TControl) target));

            CommandManager.RegisterClassCommandBinding(typeof(TControl), commandBinding);
        }
    }
}

以下是控件的默認模板。 正如您所看到的,只有兩個Button控件,每個控件都通過其Command屬性綁定到相關命令(注意這不是數據綁定,即您沒有使用{Binding}標記擴展)。

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:StackOverflow">
    <Style TargetType="{x:Type local:TestControl}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type local:TestControl}">
                    <StackPanel Orientation="Horizontal">
                        <Button Command="NavigationCommands.FirstPage" Content="First" />
                        <Button Command="NavigationCommands.LastPage" Content="Last" />
                    </StackPanel>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>

最后,這是Window 中的自定義控件。 當您單擊“First”和“Last”按鈕時,您可以通過觀察調試控制台窗口中出現的相關文本來查看正在調用的操作。

<Window x:Class="StackOverflow.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:StackOverflow">
    <local:TestControl VerticalAlignment="Top" />
</Window>

如果您以這種方式使用命令,那么您應該能夠顯着簡化您的控件代碼。

我有一個類似的問題 - 自定義控件(特別是從Control派生的類)會在實例化控件的新實例時顯示綁定錯誤。 這是因為在設置綁定之前創建了控件模板。 一旦綁定生效,控件就會開始工作。

為了“解決”這個問題(或者無論如何都要解決它),我只是在控件的構造函數中添加了對ApplyTemplate()的調用。 所以它最終看起來像這樣:

public CustomControl()
{
        InitializeComponent();
        ApplyTemplate();
}

然后就沒有更多的綁定錯誤了。

暫無
暫無

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

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