DataTemplate触发器中设置的WPF ContentControl内容随机重置为默认内容

时间:2013-10-31 18:14:57

标签: wpf datatemplate datatrigger contentcontrol

我遇到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));
    }
  }

感谢您的帮助。

1 个答案:

答案 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>