Wpf 触发器未能触发两次,DataGridCell

Wpf 触发器未能触发两次,DataGridCell,wpf,triggers,datatrigger,datagridcell,Wpf,Triggers,Datatrigger,Datagridcell,我对这一切都是新的(这是我的第一篇文章),所以请容忍我。任何改进都是值得欢迎的 我正在使用.NET4.0和VisualStudio2010编写约会计划程序。XAML由一个数据网格组成,数据网格中的行间隔为15分钟,四列(最左边的列用于表示时间)。支持数据由AppointRows组成,这是一个可观察的集合。每一行本身由一组可观察的委任人组成。我使用DragAndDrop作为输入法 DragAndDrop似乎在单元级别正常工作。可以在datagrid上删除项,从datagrid中删除项,然后在dat

我对这一切都是新的(这是我的第一篇文章),所以请容忍我。任何改进都是值得欢迎的

我正在使用.NET4.0和VisualStudio2010编写约会计划程序。XAML由一个数据网格组成,数据网格中的行间隔为15分钟,四列(最左边的列用于表示时间)。支持数据由AppointRows组成,这是一个可观察的集合。每一行本身由一组可观察的委任人组成。我使用DragAndDrop作为输入法

DragAndDrop似乎在单元级别正常工作。可以在datagrid上删除项,从datagrid中删除项,然后在datagrid中重新排列项。在datagrid上删除约会时,第一个DataTrigger将HasAppointment的自定义附加属性设置为true,第二个触发器通过将DataGridCell背景颜色设置为四种颜色之一来响应此附加属性,具体取决于单元格现在是否为空,是否包含新成员的约会,谁老了,谁需要特殊管理

问题#1:
加载DataGrid后,即使负责设置HasAppointment attached属性的DataTrigger触发,但如果以前使用过某个单元格,则负责为该单元格的背景着色的触发器不会触发。也就是说,在加载过程中,当未使用的单元格上删除约会时,所有触发器都能正常工作。然而在第一次加载或删除新约会时触发单元格后,负责背景着色的触发器将不会触发,尽管第一个触发器正确设置了HasAppointment attached属性,并且单元格将返回到第一个约会的第一个背景着色包含。要明确的是:

  • 数据网格已加载。单元格的默认颜色为绿色背景,为空
  • 用户在单元格上删除新约会。所有的触发点都是火,电池是黄色的
  • 然后,用户将该单元格的内容拖动到网格上的另一个位置。正确显示约会的单元格报告为空,HasAppointment为false,颜色返回默认绿色背景
  • 用户现在在该单元格上删除另一个约会。负责设置HasAppointment附加属性的DataTrigger将正确激发,并将单元格HasAppointment设置为true
  • 着色触发器不会触发,单元格将返回其包含的第一个约会的黄色背景 问题2:
    删除单元格上的约会后,用于设置HasAppointment Associated属性的DataTrigger Resossible将运行两次,然后才允许负责着色的触发器运行。它运行正常,但为什么要运行两次

    问题三:
    每个单元格可能有约会,也可能没有约会。每个有并置的单元可能是三个并置器中的一个。那么,在不重复每个值的代码的情况下,从转换器向DataTrigger返回多个不同值的最佳方法是什么?此时我正在使用一个触发器,当它工作时,它工作得很好

    问题四:
    DataTrigger可以对附加的属性进行操作吗?如果可以,绑定集是如何设置的

    谢谢你对这些事情的帮助

    以下是XAML:

    <Window x:Class="Chaos.Scheduler.Scheduler"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:local="clr-namespace:Chaos.Scheduler"
            xmlns:my1="clr-namespace:UpDownCtrls;assembly=UpDownCtrls"
            Title="Scheduler" Height="556" Width="1024"
            DataContext="{Binding RelativeSource={RelativeSource Self}}"
            WindowStartupLocation="CenterScreen">
    
        <!--Resources is a dictionary. so it allows assigning an x:Key value to Styles.-->
        <Window.Resources>
            <!--Declare converters for custom binding logic-->
            <local:RescheduleConverter x:Key="rescheduleConverter" />
            <local:ColorConverter x:Key="colorConverter" />
            <local:AppointmentConverter x:Key="appointmentConverter" />
            <local:WatchApppointmentNamesConverter x:Key="watchAppointmentNamesConverter" />
    
            <!--Show buttons as Red when an edit has occured-->
            <Style x:Key="SaveButtonStyle" TargetType="{x:Type Button}">
                <Style.Triggers>
                    <DataTrigger Binding="{Binding HasEdits}" Value="True" >
                        <Setter Property="Background" Value="Red" />
                    </DataTrigger>
                </Style.Triggers>
            </Style>
    
            <!--The XAML will create a "Style" object/instance of type "DataGridCell". Without an x:Key value, this "style" object
                     will be used as the parent style for all the DataGridCells. -->
            <Style TargetType="{x:Type DataGridCell}" x:Key="AppointmentStyle" >
                <EventSetter Event="PreviewMouseLeftButtonDown" Handler="DataGridCell_PreviewMouseLeftButtonDown" />
                <EventSetter Event="MouseMove" Handler="DataGridCell_MouseMove" />
                <EventSetter Event="DragEnter" Handler="DataGridCell_DragEnter" />
                <EventSetter Event="Drop" Handler="DataGridCell_Drop" />
                <!--Must use Right/LeftButtonUp and not Right/LeftButtonDown as these events are Direct, not tunneling or bubbled.-->
                <!--This makes no sense and is probably a bug, but it works-->
                <EventSetter Event="PreviewMouseRightButtonUp" Handler="DataGridCell_PreviewMouseRightButtonUp" />
    
                <!--There is no way to replace only part of the visual tree of a control. To change the visual tree of a control you must set the
                            Template property of the control to its new and COMPLETE ControlTemplate. -->
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate TargetType="{x:Type DataGridCell}">
                            <!--Define the DataGridCell content. Bind the Grid control background color to the DataGridCell Style template.
                                        The Background color will be inherited by the textblock (that is within the <Grid> by default).-->
                            <Grid Background="{TemplateBinding Background}">
                                <ContentPresenter AllowDrop="True" />
                            </Grid>
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
    
                <!--Set default background color for the entire row-->
                <Setter Property="Background" Value="Green"/>
    
                <Style.Triggers>
                    <Trigger Property="local:Scheduler.HasAppointment" Value="true">
                            <Setter Property="Background">
                                <Setter.Value>
                                    <MultiBinding Converter="{StaticResource colorConverter}" >
                                        <MultiBinding.Bindings>
                                            <!--Sends the "DataGirdCell" object to the converter-->
                                            <Binding RelativeSource="{RelativeSource Self}"></Binding>
                                            <!--Sends the current "DataGridRow" to the converter. The DataGridRow is the DataContext for the cell -->
                                            <Binding RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type DataGridRow}, AncestorLevel=1}"  Path="." />
                                        </MultiBinding.Bindings>
                                    </MultiBinding>
                                </Setter.Value>
                            </Setter>
                        </Trigger>
    
    
                    <!--If the Converter returns True, then the Background will be red for the row-->
                    <DataTrigger Value="True">
                        <DataTrigger.Binding>
                            <!--The DataTrigger is operating on the entire row-->
                            <MultiBinding Converter="{StaticResource watchAppointmentNamesConverter}" ConverterParameter="0">
                                <!--send the DataGridCell in effect when the displayname properites change-->
                                <Binding RelativeSource="{RelativeSource Self}"></Binding>
                                <!--binding will watch the displaynames for each column. The AppointmentRow is the DataContext for all cells-->
                                <Binding Path="[0].displayname"></Binding>
                                <Binding Path="[1].displayname"></Binding>
                                <Binding Path="[2].displayname"></Binding>
                            </MultiBinding>
                        </DataTrigger.Binding>
                        <!--Any setter here will be applied to the entire row, not just the cell. There MUST HAVE be setter for the Converter to be executed-->
                        <Setter Property="Background" Value="Red"/>
                    </DataTrigger>
                </Style.Triggers>
    
            </Style>
        </Window.Resources>
    
        <Grid Name="MainGrid">
            <!--Set AllowDrop="False" to show the scrollbars as not droppable targets-->
            <DataGrid AutoGenerateColumns="False" CellStyle="{StaticResource AppointmentStyle}" ItemsSource="{Binding MySchedule.AppointmentRows}" 
                      HorizontalAlignment="Stretch" Margin="302,47,0,37"
                      Name="dataGridScheduler" VerticalAlignment="Stretch" Width="688" 
                      AllowDrop="False" SelectionUnit="Cell" SelectionMode="Single" 
                      CanUserReorderColumns="False" CanUserAddRows="False" IsReadOnly="True" >
    
                <!--Must have AllowDrop = True to allow dropping on the cells-->
                <DataGrid.RowStyle>
                    <Style TargetType="{x:Type DataGridRow}">
                        <Setter Property="AllowDrop" Value="True" />
                    </Style>
                </DataGrid.RowStyle>
    
                <DataGrid.Columns>
                    <!--{Binding /, Path=[0].displayname} is binding to the current item of the collection, the appointment row,
                    with index of 0, the appointment, and shows the property of appointment displayname. The displayname must use the INotifyPropertyChanged interface-->
                    <DataGridTextColumn Binding="{Binding time}" Header="Time" Width="58" IsReadOnly="True"     />
                    <DataGridTextColumn Binding="{Binding /, Path=[0].displayname}" Header="Name" Width="240*"  />
                    <DataGridTextColumn Binding="{Binding /, Path=[1].displayname}" Header="Name" Width="240*"  />
                    <DataGridTextColumn Binding="{Binding /, Path=[2].displayname}" Header="Name" Width="240*"  />
                </DataGrid.Columns>
            </DataGrid>
    
    …..........
    </Window>
    
    
    …..........
    
    下面是隐藏的代码:

     #region Attached Property HasAppointment
        // Attached properties are added to the control, not the data being displayed, and used by the XAML directly.
        // Example useage in XAML: <Trigger Property="local:Scheduler.HasAppointment" Value="True">
        // The attached property template is obtained by typing <propa> tab tab into the code-behind.
        public static Boolean GetHasAppointment(DependencyObject obj)
        {
            return (Boolean)obj.GetValue(HasAppointmentProperty);
        }
    
        public static void SetHasAppointment(DependencyObject obj, Boolean value)
        {
            obj.SetValue(HasAppointmentProperty, value);
        }
    
        // Using a DependencyProperty as the backing store for HasAppointment.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty HasAppointmentProperty =
            DependencyProperty.RegisterAttached("HasAppointment", typeof(Boolean), typeof(Scheduler), new UIPropertyMetadata(false));
    
        #endregion
    
    
    /* Converters apply custom logic to the XAML Bindings */
    
    /// <summary>
    /// Tests the DataGridCell for a displayname. Returns false for no displayname, true for a display name.
    /// </summary>
    [ValueConversion(typeof(String), typeof(Boolean))]
    public class AppointmentConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            String displayname = value as String;
    
            if (string.IsNullOrEmpty(displayname))
                return false;
    
            return true;
        }
    
        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
    
    
    /// <summary>
    /// Label the reschedule shape with "Reschedule Appointment" or the patient name.
    /// </summary>
    [ValueConversion(typeof(Appointment), typeof(String))]
    public class RescheduleConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            Appointment apt = value as Appointment;
            if (apt.displayname == null) return "Reschedule Appointment";
            else return apt.displayname;
        }
    
        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
    
    /* Color a grid cell based on appointment type and patient history */
    public class ColorConverter : IMultiValueConverter
    {
        public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            if (values[1] is DataGridRow)
            {
                // The "cell" tells nothing about its contents, only its location in the grid, its column name and column position.
                // The datacontext comes from the datagrid row.
                DataGridCell cell = (DataGridCell)values[0];
                if (cell.Column.DisplayIndex > 0)
                {
                    Boolean hasappointment = Scheduler.GetHasAppointment(cell);
    
                    // the object "row" is of type AppointmentRow.
                    DataGridRow row = (DataGridRow)values[1];
    
                    // gets the physical row number of this row in the grid.
                    int rowIndex = row.GetIndex();
    
                    // SortMemberPath returns: "time",  "[0].displayname", " "[1].displayname",  "[2].displayname"
                    string columnName = cell.Column.SortMemberPath;
    
                    Appointment appointment = ((AppointmentRow)row.Item)[cell.Column.DisplayIndex - 1] as Appointment;
                    if (null != appointment.displayname)
                    {
                        if ((appointment.type & AppointmentType.Excision) == AppointmentType.Excision)
                        {
                           // return new SolidColorBrush(Colors.Red);
                            return Brushes.Red;
                        }
                        else if (null == appointment.lastvisit)
                        {
                           // return new SolidColorBrush(Colors.Yellow);
                            return Brushes.Yellow;
                        }
                    }
                }
    
            }
            // return System.Windows.SystemColors.AppWorkspaceColor;
            return new SolidColorBrush(Colors.White);
        }
    
        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
    
    
    /// <summary>
    /// Set the HasAppointment flag for all cells in the AppointmentRow. 
    /// For some reason, this routine will process the entire row twice on a drop. I suspect setting the HasAppointment flag forces the
    /// second processeing as the "cell" object itself maybe changed.
    /// [0] = DataGridCell
    /// [1] = AppointmentRow[0].displayname
    /// [2] = AppointmentRow[1].displayname
    /// [3] = AppointmentRow[2].displayname
    /// </summary>
    public class WatchApppointmentNamesConverter : IMultiValueConverter
    {
        public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            DataGridCell cell = values[0] as DataGridCell;
    
            if (!String.IsNullOrWhiteSpace((string)values[1]))
            {
                if (cell.Column.DisplayIndex == 1)
                    Scheduler.SetHasAppointment(cell, true);
            }
            else
            {
                if (cell.Column.DisplayIndex == 1)
                    Scheduler.SetHasAppointment(cell, false);
            }
    
    
            if (!String.IsNullOrWhiteSpace((string)values[2]))
            {
                if (cell.Column.DisplayIndex == 2)
                    Scheduler.SetHasAppointment(cell, true);
            }
            else
            {
                if (cell.Column.DisplayIndex == 2)
                    Scheduler.SetHasAppointment(cell, false);
            }
    
    
            if (!String.IsNullOrWhiteSpace((string)values[3]))
            {
                if (cell.Column.DisplayIndex == 3)
                    Scheduler.SetHasAppointment(cell, true);
            }
            else
            {
                if (cell.Column.DisplayIndex == 3)
                    Scheduler.SetHasAppointment(cell, false);
            }
    
            // need to return false or will force background to red.
             return false;
        }
    
    
        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
    
    #地区附属酒店
    //附加的属性被添加到控件中,而不是被显示的数据,并由XAML直接使用。
    //XAML中的示例用法:
    //附加的属性模板是通过在代码隐藏中键入tab tab获得的。
    公共静态布尔GetHasAppointment(DependencyObject obj)
    {
    返回(布尔)obj.GetValue(HasAppointmentProperty);
    }
    公共静态void SetHasAppointment(DependencyObject对象,布尔值)
    {
    对象SetValue(HasAppointmentProperty,value);
    }
    //使用DependencyProperty作为HasAppointment的后备存储。这将启用动画、样式、绑定等。。。
    公共静态只读DependencyProperty HasAppointmentProperty=
    RegisterAttached(“HasAppointment”、typeof(Boolean)、typeof(Scheduler)、新UIPropertyMetadata(false));
    #端区
    /*转换器适用于
    
                    <DataGridTextColumn.ElementStyle>
                        <Style TargetType="{x:Type TextBlock}">
                            <Setter Property="Background" >
                                <Setter.Value>
                                    <MultiBinding Converter="{StaticResource colorTextConverter}">
                                        <Binding  RelativeSource="{RelativeSource Mode=Self}" Path="Text" />
                                        <Binding  RelativeSource="{RelativeSource Mode=FindAncestor,AncestorType=DataGridCell}" Path="." />
                                    </MultiBinding>
                                </Setter.Value>
                            </Setter>
                        </Style>
                    </DataGridTextColumn.ElementStyle>
                </DataGridTextColumn>