我正在测试我需要在WPF(.NET 4.6.2)中编写的应用程序的实现。该应用程序将具有消息列表。从列表中选择一条消息后,该消息的详细信息将显示在单独的区域中。消息可以是不同的类型,因此详细信息部分的格式将有所不同。非常简单的主从布局设计。
不幸的是,由于我们使用的是中间件,所以包含消息类定义的DLL为类成员定义了字段,而不是属性。我的挑战是在不依赖绑定的情况下更新UI的详细信息部分。
为了对此进行测试,我编写了一个具有ListBox
和ContentControl
的应用程序,用于显示所选消息的详细信息。我有一个简单的Message
类,它具有一个Id
属性。对于UI,我创建了两个DataTemplate
,可以根据当前Message
的{{1}}的{{1}}值是否为0进行选择,以显示消息详细信息。 / p>
布局的XAML是:
Id
...和代码:
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:TestGui"
x:Name="window" x:Class="TestGui.MainWindow"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<local:MessageDetailSelector x:Key="MessageDetailSelector" />
<local:MessageList x:Key="MessagesList">
<local:Message/>
<local:Message Id="1"/>
<local:Message Id="2"/>
</local:MessageList>
<DataTemplate x:Key="ZeroDetailTemplate">
<StackPanel Orientation="Horizontal">
<Label>Zero Detail: </Label>
<Label x:Name="DetailLabel" />
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="DetailTemplate">
<StackPanel Orientation="Horizontal">
<Label>Detail:</Label>
<Label x:Name="DetailLabel" "/>
</StackPanel>
</DataTemplate>
</Window.Resources>
<DockPanel>
<ListBox x:Name="listBox" DockPanel.Dock="Left" MinWidth="150" ItemsSource="{Binding Source={StaticResource MessagesList}}">
<ListBox.ItemTemplate>
<DataTemplate>
<Label Content="{Binding Id}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<local:CustomContentControl x:Name="DetailControl" Content="{Binding SelectedItem, ElementName=listBox}" ContentTemplateSelector="{StaticResource MessageDetailSelector}"/>
</DockPanel>
</Window>
在大多数情况下,它可以按预期工作。我可以从using System;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media;
namespace TestGui
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
// Can be used to programmaticaly set DataTemplate:
// - add SelectionChanged="MessageSelectionChanged to <ListBox> in XAML
// - comment out ContentTemplateSelector from <local:CustomContentControl>
private void MessageSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (e.AddedItems.Count > 0 && e.AddedItems[0] is Message msg)
{
DetailControl.ContentTemplate = msg.Id == 0 ?
FindResource("ZeroDetailTemplate") as DataTemplate :
FindResource("DetailTemplate") as DataTemplate;
DetailControl.Content = msg;
}
else
{
DetailControl.ContentTemplate = null;
}
}
}
class CustomContentControl : ContentControl
{
protected override void OnContentChanged(object oldContent, object newContent)
{
base.OnContentChanged(oldContent, newContent);
ApplyTemplate();
var cp = VisualTreeHelper.GetChild(this, 0) as ContentPresenter;
System.Diagnostics.Debug.Assert(cp != null);
DataTemplate dt = cp.ContentTemplate;
Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Normal,
(Action)(() =>
{
try
{
dt.FindName("DetailLabel", this);
}
catch (Exception e)
{
Console.WriteLine($"OnContentChanged: Cannot find DetailLabel\n{e.Message}");
}
}));
}
protected override void OnContentTemplateChanged(DataTemplate oldContentTemplate, DataTemplate newContentTemplate)
{
base.OnContentTemplateChanged(oldContentTemplate, newContentTemplate);
ApplyTemplate();
var cp = VisualTreeHelper.GetChild(this, 0) as ContentPresenter;
System.Diagnostics.Debug.Assert(cp != null);
DataTemplate dt = cp.ContentTemplate;
Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Normal,
(Action)(() =>
{
try
{
dt.FindName("DetailLabel", this);
}
catch (Exception e)
{
Console.WriteLine($"OnContentTemplateChanged: Cannot find DetailLabel\n{e.Message}");
}
}));
}
}
class Message
{
public int Id { get; set; } = 0;
}
class MessageList : ObservableCollection<Message> { }
class MessageDetailSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
if (container is FrameworkElement element && item is Message message)
{
if (message.Id == 0)
{
return element.FindResource("ZeroDetailTemplate") as DataTemplate;
}
else
{
return element.FindResource("DetailTemplate") as DataTemplate;
}
}
return null;
}
}
}
中选择一条消息,然后根据ID显示正确的模板。但是,我现在需要能够手动更新ListBox
中控件的值。
我创建了DataTemplate
类,因此可以覆盖CustomContentControl
的方法,并尝试找出可以在何处拦截模板以对其进行更新。根据{{3}},我应该可以在ContentControl
上致电FindName
。
在本示例中实现,使用ContentPresenter
自动选择模板,正在调用MessageDetailSelector
方法。但是,这会导致异常,因为此行中的OnContentChanged
引用是ContentTemplate
:
null
我尝试从DataTemplate dt = cp.ContentTemplate;
元素中删除ContentTemplateSelector
属性,并将<local:CustomContentControl>
的{{1}}属性设置为我的SelectionChanged
方法。这导致了ListBox
,这导致我进入this MSDN article。
我尝试使用MessageSelectionChanged
调用InvalidOperationException
(返回ApplyTemplate
),但是仍然遇到相同的异常。
任何关于我可以做什么的想法都可以正确掌握false
控件,甚至对关于无需绑定即可更新控件的另一种方法的想法也将不胜感激。