我遇到ContentControl内容的问题,其内容是使用DataTriggers将内容随机重置为DataTemplate中指定的默认内容而设置的。
场景是我在网络上有一堆设备(传感器),我需要检查状态等等。根据传感器的状态,我可能想要显示彩色圆圈(绿色,红色或黄色)或图像。例如,如果传感器正被我想要显示代表用户的图像的人使用。如果传感器可用于连接,我想显示绿色椭圆等
我目前正在使用WPF DataGrid来显示传感器列表及其状态,尽管我使用ListBox和ListView获得了相同的错误行为(尚未尝试过普通的ItemsControl)。仅供参考,传感器异步进出。
如果运行示例代码,您将看到的是,最初连接状态为CONNECTED的项目将首先显示所需的图像。随着行添加到网格中,图像随机消失,并替换为DataTemplate中指定的默认内容。仅当内容中有图像时才会出现此问题。其他州工作得很好。
下面是我认为您需要查看行为的所有代码(xaml,viewmodel,model)。很抱歉下面发布的代码数量。我试图尽可能地配对它以说明问题。希望通过查看XAML可以明显地解决问题。如果您愿意,剩下的源代码将帮助您更快地启动和运行。
这是Window XAML:
<Window x:Class="StackOverflowGridIssue.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:model="clr-namespace:StackOverflowGridIssue.Model"
xmlns:shape="http://schemas.microsoft.com/expression/2010/drawing"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d" Width="500"
Title="MainWindow" >
<Grid>
<DataGrid Name="_SensorsDataGrid" ItemsSource="{Binding Sensors}"
AutoGenerateColumns="False" HeadersVisibility="Column" >
<DataGrid.Columns>
<!-- Status -->
<DataGridTemplateColumn Header="Status" MinWidth="50"
Width="SizeToHeader" IsReadOnly="True">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ContentControl x:Name="myContent" Background="LimeGreen"
Width="25" Height="25">
<ContentControl.ToolTip>
<TextBlock Text="{Binding ConnectionState, Mode=OneWay}"
Foreground="Black" />
</ContentControl.ToolTip>
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}" BasedOn="{x:Null}" >
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ContentControl}">
<Grid>
<Ellipse Height="10" Width="10"
Fill="{TemplateBinding Background}"
Stroke="{TemplateBinding Background}" />
<ContentPresenter />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ContentControl.Style>
</ContentControl>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding ConnectionState, Mode=OneWay}"
Value="{x:Static model:ConnectionStateType.NOT_FOUND}">
<Setter TargetName="myContent" Property="Background" Value="Red" />
</DataTrigger>
<DataTrigger Binding="{Binding ConnectionState, Mode=OneWay}"
Value="{x:Static model:ConnectionStateType.AVAILABLE}">
<Setter TargetName="myContent" Property="Background"
Value="GREEN" />
</DataTrigger>
<DataTrigger Binding="{Binding ConnectionState, Mode=OneWay}"
Value="{x:Static model:ConnectionStateType.CONNECTED}">
<Setter TargetName="myContent" Property="Content">
<Setter.Value>
<Image Source="Images/User.png" />
</Setter.Value>
</Setter>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<!-- DEBUG Connection State -->
<DataGridTextColumn Header="DEBUG"
Binding="{Binding ConnectionState}" Width="SizeToCells" />
<!-- Sensor Name -->
<DataGridTextColumn Header="Sensor Name"
Binding="{Binding Name}" Width="SizeToCells" />
<!-- IPAddress -->
<DataGridTextColumn Header="IP Address"
Binding="{Binding IPAddress}" Width="SizeToCells" />
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
这里是App.xaml.cs,我在其中引导所有内容并模拟异步发现的传感器(fyi,如果我连续加载它们会出现同样的问题,只需要一次一个地缓慢加载传感器就更容易了解:< / p>
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
public const int NUM_SENSORS = 100;
Random _connectionStateGenerator = new Random();
ConnectionStateType _connectionState = ConnectionStateType.AVAILABLE;
SensorViewModel viewModel;
Timer _timer = new Timer(300);
int _index = 1;
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
// Get a handle to the main view (MainWindow)
Window window = new MainWindow();
viewModel = new SensorViewModel();
//Loads sensors synchronously (same issue)
//AddSensors(viewModel.Sensors);
window.DataContext = viewModel;
window.Show();
//Simulate async sensor discovery (more real world example)
_timer.Enabled = false;
_timer.AutoReset = true;
_timer.Elapsed += (s, args) =>
{
Dispatcher.InvokeAsync(new Action( () =>
{
viewModel.Sensors.Add(
new Sensor("Sensor" + _index, "192.168.1." + _index,
(ConnectionStateType)_connectionStateGenerator.Next(0, 3)));
if (_index++ > NUM_SENSORS)
_timer.Enabled = false;
}));
};
_timer.Enabled = true;
}
//Helper for loading synchronously rather than asynchronously
private void AddSensors(ObservableCollection<Model.Sensor> sensors)
{
for (int i = 0; i < NUM_SENSORS; i++)
{
_connectionState = (ConnectionStateType)_connectionStateGenerator
.Next(0, 5);
sensors.Add(
new Sensor("Sensor" + i, "192.168.1." + i, _connectionState));
}
}
}
这是代表传感器的型号代码:
public enum ConnectionStateType
{
NOT_FOUND,
AVAILABLE,
CONNECTED,
}
public class Sensor : INotifyPropertyChanged
{
string _name = "Unknown";
string _IPAddress;
ConnectionStateType _connectionState = ConnectionStateType.AVAILABLE;
public Sensor(string name, string IPAddress, ConnectionStateType connectionState)
{
_name = name;
_IPAddress = IPAddress;
_connectionState = connectionState;
}
public ConnectionStateType ConnectionState
{
get { return _connectionState; }
set
{
if (value == _connectionState) return;
_connectionState = value;
NotifyPropertyChanged("ConnectionState");
}
}
public string Name
{
get { return _name; }
set
{
if (value == _name) return;
_name = value;
NotifyPropertyChanged("Name");
}
}
public string IPAddress
{
get { return _IPAddress; }
set
{
if (value == _IPAddress) return;
_IPAddress = value;
NotifyPropertyChanged("IPAddress");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string property)
{
var handler = PropertyChanged;
if(handler != null)
handler(this, new PropertyChangedEventArgs(property));
}
}
这是视图模型:
public class SensorViewModel : INotifyPropertyChanged
{
ObservableCollection<Sensor> _sensors = new ObservableCollection<Sensor>();
public ObservableCollection<Sensor> Sensors
{
get { return _sensors; }
private set
{
if (value == _sensors) return;
_sensors = value;
NotifyPropertyChanged("Sensors");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string property)
{
var handler = PropertyChanged;
if(handler != null)
handler(this, new PropertyChangedEventArgs(property));
}
}
感谢您的帮助。
答案 0 :(得分:0)
解决方案最终是让触发器交换整个模板,而不是尝试在模板中设置内容区域。
这是一个更清晰的XAML,其中定义了模板资源,以及一个使用这些资源的DataGrid:
<UserControl x:Class="StackOverflowGridIssue.SensorGrid"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:model="clr-namespace:StackOverflowGridIssue.Model"
xmlns:shape="http://schemas.microsoft.com/expression/2010/drawing"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300">
<UserControl.Resources>
<ControlTemplate x:Key="DefaultConnectionStateTemplate"
TargetType="{x:Type ContentControl}">
<Grid>
<Ellipse Height="10" Width="10"
Fill="{TemplateBinding Background}"
Stroke="{TemplateBinding Background}" />
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
</Grid>
</ControlTemplate>
<ControlTemplate x:Key="ConnectedTemplate"
TargetType="{x:Type ContentControl}" x:Shared="false">
<Grid>
<Image Source="Images/User.png" Height="25" Width="25"/>
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
</Grid>
</ControlTemplate>
<DataTemplate x:Key="SensorConnectionStateTemplate">
<ContentControl x:Name="myContent">
<ContentControl.ToolTip>
<TextBlock Text="{Binding ConnectionState, Mode=OneWay}" />
</ContentControl.ToolTip>
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}" BasedOn="{x:Null}" >
<Setter Property="Template"
Value="{StaticResource DefaultConnectionStateTemplate}" />
</Style>
</ContentControl.Style>
</ContentControl>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding ConnectionState, Mode=OneWay}"
Value="{x:Static model:ConnectionStateType.NOT_FOUND}">
<Setter TargetName="myContent" Property="Background" Value="Red" />
<Setter TargetName="myContent" Property="Content" Value="Not Found" />
</DataTrigger>
<DataTrigger Binding="{Binding ConnectionState, Mode=OneWay}"
Value="{x:Static model:ConnectionStateType.AVAILABLE}">
<Setter TargetName="myContent" Property="Background" Value="GREEN" />
<Setter TargetName="myContent" Property="Content" Value="Available" />
</DataTrigger>
<DataTrigger Binding="{Binding ConnectionState, Mode=OneWay}"
Value="{x:Static model:ConnectionStateType.CONNECTED}">
<Setter TargetName="myContent" Property="Template"
Value="{StaticResource ConnectedTemplate}" />
<Setter TargetName="myContent" Property="Content" Value="Connected" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</UserControl.Resources>
<Grid>
<DataGrid Name="_SensorsDataGrid" AutoGenerateColumns="False"
ItemsSource="{Binding Sensors}"
HeadersVisibility="Column" >
<DataGrid.Columns>
<!-- Status -->
<DataGridTemplateColumn Header="Status" MinWidth="50"
Width="SizeToHeader" IsReadOnly="True"
CellTemplate="{StaticResource SensorConnectionStateTemplate}" />
<!-- DEBUG Connection State -->
<DataGridTextColumn Header="DEBUG"
Binding="{Binding ConnectionState}" Width="SizeToCells" />
<!-- Sensor Name -->
<DataGridTextColumn Header="Sensor Name"
Binding="{Binding Name}" Width="SizeToCells" />
<!-- IPAddress -->
<DataGridTextColumn Header="IP Address" Width="SizeToCells"
Binding="{Binding IPAddress}" />
</DataGrid.Columns>
</DataGrid>
</Grid>
</UserControl>