簡體   English   中英

WPF Caliburn.Micro 和 TabControl 與 UserControls 問題

[英]WPF Caliburn.Micro and TabControl with UserControls issue

我很確定這已經在某處得到了回答,但我似乎無法在我的生活中找到它。

我正在嘗試使用 TabControl 在 UserControls 之間切換(每個選項卡都不同,所以不使用 Items)

這是細分:我有我的主視圖和 3 個用戶控件。 Mainview 有一個選項卡控件 - 每個選項卡應顯示不同的用戶控件。

我可以輕松地將 tabcontrol contect 設置為 usercontrol 使用 But then it is not bound to the viewmodel, only the view.

所以我在我的 VM 和 ActivateItem 中使用導體。 這就是它開始變得奇怪/令人沮喪的地方。 應用程序開始時選擇 Tab0,但 Tab2(最后一個選項卡)內容。 單擊任何其他選項卡,為該選項卡加載正確的 ViewModel。 單擊返回 Tab0,也在那里加載正確的內容。

我如何才能停止這種情況? 另外,如果切換選項卡不會再次重新初始化視圖模型,清除已經輸入的字段,我真的很喜歡它。

無論如何,這是我的一些來源,我將把它放在這里並在我弄壞鼠標之前做其他事情。

看法:

<TabControl HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Grid.Row ="1">
        <TabItem Header="PC Information">
            <Grid>
                <ContentControl x:Name="LoadRemoteInfo" cal:View.Model="{Binding ActiveItem}"/>
            </Grid>
        </TabItem>
        <TabItem Header="Remote Tools">
            <Grid>
                <ContentControl x:Name="LoadRemoteTools" cal:View.Model="{Binding ActiveItem}"/>
            </Grid>
        </TabItem>
        <TabItem Header="CHRemote">
            <Grid>
                <ContentControl x:Name="LoadCHRemote" cal:View.Model="{Binding ActiveItem}"/>
            </Grid>
        </TabItem>

    </TabControl>

和視圖模型:

class MainViewModel : Conductor<object>
{
    RemoteInfoViewModel remoteInfo = new RemoteInfoViewModel();
    RemoteToolsViewModel remoteTools = new RemoteToolsViewModel();
    CHRemoteViewModel chRemote = new CHRemoteViewModel();

    public MainViewModel()
    {
        ActivateItem(remoteInfo);
    }

    public void LoadRemoteInfo()
    {
        ActivateItem(remoteInfo);
    }

    public void LoadRemoteTools()
    {
        ActivateItem(remoteTools);
    }

    public void LoadCHRemote()
    {
        ActivateItem(chRemote);
    }
}

我可以建議一條不同的路線嗎?

這是我在主要細節場景中成功完成的事情。 假設您有一組子視圖模型。 我將為所有這些項目准備一個標記界面,當然,如果有這樣的方法跨越所有子視圖模型,您可以添加您認為合適的屬性/方法:

public interface IMainScreenTabItem : IScreen
{
}

您可以確定您希望所有子模型都是Screen (或者,如果是嵌套方案, Conductor )。 它使它們具有完整的初始化/激活/停用循環。

然后,子視圖模型:

public sealed class ChRemoteViewModel : Screen, IMainScreenTabItem
{
    public ChRemoteViewModel()
    {
        DisplayName = "CH Remote";
    }
}

public sealed class PcInfoViewModel : Screen, IMainScreenTabItem
{
    public PcInfoViewModel()
    {
        DisplayName = "PC Info";
    }
}

public sealed class RemoteToolsViewModel : Screen, IMainScreenTabItem
{
    public RemoteToolsViewModel()
    {
        DisplayName = "Remote Tools";
    }
}

DisplayName將顯示為標題文本。 將這些類密封起來是一個很好的做法,因為DisplayName是一個虛擬屬性,並且在一個未密封的類的構造函數中調用虛方法是一個很大的禁忌。

然后,您可以添加相應的視圖並設置您選擇注冊的IoC容器 - 您必須將所有子視圖模型注冊為實現IMainScreenTabItem類,然后:

public class MainViewModel : Conductor<IMainScreenTabItem>.Collection.OneActive
{
    public MainViewModel(IEnumerable<IMainScreenTabItem> tabs)
    {
        Items.AddRange(tabs);
    }
}

MainView.xaml只是:

<TabControl Name="Items"/>

它只是有效。 如果您的子視圖模型具有多個依賴關系(例如數據庫訪問,記錄器,驗證機制等),那么它也是非常好的和方便的解決方案,現在您可以讓IoC完成所有繁重工作,而不是手動實例化它們。

但有一點是:選項卡的放置順序與注入類的順序相同。 如果您希望控制排序,可以通過傳遞自定義IComparer<IMainScreenTabItem>或添加一些屬性OrderBy或選擇IMainScreenTabItem接口,在MainViewModel構造函數中對它們進行排序。 默認選定的項目將是“ Items列表中的第一個。

其他選項是使MainViewModel采用三個參數:

public MainViewModel(ChRemoteViewModel chRemoteViewModel, PcInfoViewModel pcInfo, RemoteToolsViewModel remoteTools)
{
    // Add the view models above to the `Items` collection in any order you see fit
}

雖然當你擁有超過2到3個兒童視圖模型(並且你可以輕松獲得更多)時,它會很快變得混亂。

關於'清算'部分。 由IoC創建的視圖模型與常規生命周期相關:它們最多初始化一次( OnInitialize ),然后每次從OnDeactivate(bool)導航時停用,並在導航到( OnActivate )時激活。 OnDeactivatebool參數指示視圖模型是剛剛停用還是完全“關閉”(例如,當您關閉對話窗口並離開時)。 如果完全關閉視圖模型,它將在下次顯示時重新初始化。

這意味着在OnActivate調用之間將保留任何綁定數據,您必須在OnDeactivate明確清除它。 更重要的是,如果您保持對子視圖模型的強引用,那么即使在您調用OnDeactivate(true) ,數據仍將在下次初始化時出現 - 這是因為IoC注入視圖模型創建一次 (除非您注入工廠以Func<YourViewModel>的形式運行,然后根據需要初始化/激活/停用。


編輯

關於bootstrapper,我不太清楚你正在使用什么樣的IoC容器。 我的示例使用SimpleInjector ,但您可以使用例如Autofac輕松完成相同的操作:

public class AppBootstrapper : Bootstrapper<MainViewModel>
{
    private Container container;

    /// <summary>
    /// Override to configure the framework and setup your IoC container.
    /// </summary>
    protected override void Configure()
    {
        container = new Container();
        container.Register<IWindowManager, WindowManager>();
        container.Register<IEventAggregator, EventAggregator>();
        var viewModels =
            Assembly.GetExecutingAssembly()
                .DefinedTypes.Where(x => x.GetInterface(typeof(IMainScreenTabItem).Name) != null && !x.IsAbstract && x.IsClass);
        container.RegisterAll(typeof(IMainScreenTabItem), viewModels);
        container.Verify();
    }

    /// <summary>
    /// Override this to provide an IoC specific implementation.
    /// </summary>
    /// <param name="service">The service to locate.</param><param name="key">The key to locate.</param>
    /// <returns>
    /// The located service.
    /// </returns>
    protected override object GetInstance(Type service, string key)
    {
        if (service == null)
        {
            var typeName = Assembly.GetExecutingAssembly().DefinedTypes.Where(x => x.Name.Contains(key)).Select(x => x.AssemblyQualifiedName).Single();

            service = Type.GetType(typeName);
        }
        return container.GetInstance(service);
    }

    protected override IEnumerable<object> GetAllInstances(Type service)
    {
        return container.GetAllInstances(service);
    }

    protected override void BuildUp(object instance)
    {
        container.InjectProperties(instance);
    }
}

請注意ConfigureviewModels注冊。

只是為了補充 Patryk Ćwiek 的好答案!

如果您已經在使用 Caliburn Mirco 並且不想添加更多依賴項,您可以使用它們的SimpleContainer代替 SimpleInjector 或 AutoFac。

只需像這樣注冊實現:

container.AllTypesOf<IMainScreenTabItem>(Assembly.GetExecutingAssembly());

暫無
暫無

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

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