扩展器控件中切换按钮的可见性无法正常工作

Visibility of toggle button in expander control is not working properly

在处理特定项目时,我使用了带有扩展器的 TypeListView 自定义控件(即 TypeListView : ListView),如下所示:

<DockPanel x:Name="MyDockPanel"  Grid.Column="0" Grid.Row="1" Margin="0,0,0,0" HorizontalAlignment="Stretch" DataContext="{Binding Path=MainViewModel.TypeSelectionViewModel}">
                        <res:TypesListView x:Name="TypeListView"
                               BorderThickness="0,1,0,0"
                               ItemsSource="{Binding Path=Types}"
                               SelectedItem="{Binding Path=SelectedType, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
                               SelectionChanged="TypeListView_SelectionChanged"
                               DockPanel.Dock="Left"
                               SelectionMode="Extended"
                               IsSynchronizedWithCurrentItem="True"
                               HorizontalAlignment="Stretch"
                               ScrollViewer.HorizontalScrollBarVisibility="Auto"
                               ScrollViewer.VerticalScrollBarVisibility="Auto"
                               HorizontalContentAlignment="Stretch"
                               ContextMenu="{StaticResource ListViewContextMenu}"
                               ItemContainerStyle="{StaticResource HeaderedListViewItemStyle}"
                               dragDrop:DragDrop.IsDragSource="True" >

                            <ListView.ItemTemplate>
                                <DataTemplate DataType="{x:Type vm:ListViewItemViewModel }">
                                    <!--<Expander HorizontalContentAlignment="Stretch" HorizontalAlignment="Stretch">-->
                                    <Expander BorderThickness="0"
                                  HorizontalAlignment="Stretch" 
                                  Header="{Binding}" 
                                  Style="{StaticResource ExpanderStyle}"
                                  IsExpanded="{Binding Path=IsExpanded, Mode=TwoWay}" >

                                        <res:TypesListView BorderThickness="0"
                                      ItemsSource="{Binding Variants}"
                                      ItemContainerStyle="{StaticResource ExpandedListViewItemStyle}"
                                      dragDrop:DragDrop.IsDragSource="True"
                                      SelectedItem="{Binding Path=TypeSelectionViewModel.SelectedType, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
                                            <ListView.ItemTemplate>
                                                <DataTemplate>
                                                    <DockPanel Margin="20,0,0,0" HorizontalAlignment="Stretch" 
                                                   ToolTip="{Binding Path=ConsistencyState, Converter={StaticResource ConsistencyStateToToolTipConverter}}">
                                                        <Image Margin="0,0" Source="{Binding Icon}"/>
                                                        <TextBlock Margin="3,0" Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}"
                                                       Foreground="{Binding Path=ConsistencyState, Converter={StaticResource ConsistencyStateToColorConverter}}"/>
                                                        <Image Opacity="0.75" HorizontalAlignment="Right" Margin="0,0" 
                                                   Source="{Binding Path=TypeSelectionViewModel.MainViewModel.DragDropIcon}" 
                                                   ToolTip="{x:Static res:TextLibrary.TEXT_DRAGCOPYCMT}">
                                                            <Image.Visibility>
                                                                <MultiBinding Converter="{StaticResource DragDropIconVisibilityConverter}">
                                                                    <Binding Path="TypeSelectionViewModel.SelectedType" />
                                                                    <Binding Path="." />
                                                                </MultiBinding>
                                                            </Image.Visibility>
                                                        </Image>
                                                    </DockPanel>
                                                </DataTemplate>
                                            </ListView.ItemTemplate>
                                        </res:TypesListView>
                                        <!--<DataTemplate  >-->
                                        <!--<DockPanel HorizontalAlignment="Stretch" 
                                           ToolTip="{Binding Path=ConsistencyState, Converter={StaticResource ConsistencyStateToToolTipConverter}}">

                                                <Image Margin="0,0" Source="{Binding Icon}"/>
                                                <TextBlock Margin="3,0" Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}"
                                                       Foreground="{Binding Path=ConsistencyState, Converter={StaticResource ConsistencyStateToColorConverter}}"/>
                                                <Image Opacity="0.75" HorizontalAlignment="Right" Margin="0,0" 
                                                   Source="{Binding Path=TypeSelectionViewModel.MainViewModel.DragDropIcon}" 
                                                   ToolTip="{x:Static res:TextLibrary.TEXT_DRAGCOPYCMT}">
                                                    <Image.Visibility>
                                                        <MultiBinding Converter="{StaticResource DragDropIconVisibilityConverter}">
                                                            <Binding Path="TypeSelectionViewModel.SelectedType" />
                                                            <Binding Path="." />
                                                        </MultiBinding>
                                                    </Image.Visibility>
                                                </Image>

                                            </DockPanel>-->
                                        <!--</DataTemplate>-->

                                    </Expander>
                                </DataTemplate>
                            </ListView.ItemTemplate>
                        </res:TypesListView>
                    </DockPanel>

TypeListView 的项目源是 "Types",其中 "Types" 是 listviewItemViewModel 的可观察集合。 现在,我已经使用扩展器来扩展每个 ListViewItemViewModel,当用户单击扩展按钮时,它可以再次拥有一个可观察的 listviewitemviewmodel 集合。

只是 ListViewItemViewModel 的一小段以及类型到底是什么, 只是粗略的代码:

class ListViewItemViewMOdel
    {
     public string Name
            {
                return "SomeString";
            }
    private ObservableCollection<ListViewItemViewModel> _variants;

            public ObservableCollection<ListViewItemViewModel> Variants
            {
                get
                {
                    return _variants;
                }
            }
    }

类型选择视图模型Class:

public class TypeSelectionViewModel : INotifyPropertyChanged
    {
        #region Fields

        private readonly MainViewModel _mainViewModel;
        private readonly ObservableCollection<ListViewItemViewModel> _types = new ObservableCollection<ListViewItemViewModel>();
        private List<ListViewItemViewModel> _selectedTypes = new List<ListViewItemViewModel>();
        private ListViewItemViewModel _selectedType;



        #endregion (Fields)


        public ListViewItemViewModel SelectedType
        {
            get { return _selectedType; }
            set
            {
                if (_selectedType != value)
                {
                    //ensure that only one Item can be selected (e.g. unset selection if a variant gets selected)
                    if (_selectedType != null && _selectedType.IsSelected)
                        _selectedType.IsSelected = false;

                    _selectedType = value;

                    if (_selectedType != null)
                    {
                        _mainViewModel.TypeStructureViewModel.RootTreeViewElement.Add(
                            new TreeViewItemViewModel(_selectedType.AutomationObjectBase, null, _mainViewModel));

                        if (_mainViewModel.TypeStructureViewModel.RootTreeViewElement.Count > 1)
                            _mainViewModel.TypeStructureViewModel.RootTreeViewElement.RemoveAt(0);

                        // select root element in TreeView and expand it
                        _mainViewModel.TypeStructureViewModel.RootTreeViewElement[0].IsExpanded = true;
                        //_mainViewModel.TypeStructureViewModel.ListSelected = new List<TreeViewItemViewModel>() { _mainViewModel.TypeStructureViewModel.RootTreeViewElement[0]};
                        _mainViewModel.TypeStructureViewModel.RootTreeViewElement[0].IsSelected = true;
                    }
                    else
                    {
                        // clear the treeview
                        _mainViewModel.TypeStructureViewModel.RootTreeViewElement.Clear();
                        //_mainViewModel.TypeStructureViewModel.ListSelected = new List<TreeViewItemViewModel>();
                    }

                    OnPropertyChanged("SelectedType");
                }
            }
        }

        public List<ListViewItemViewModel> SelectedTypes
        {
            get { return _selectedTypes; }
            set { _selectedTypes = value; }
        }


        public ObservableCollection<ListViewItemViewModel> Types
        {
            get { return _types; }
        }

        public MainViewModel MainViewModel
        {
            get { return _mainViewModel; }
        }

        public ICollectionView TypeCollectionView 
        { 
            get
            {
                return CollectionViewSource.GetDefaultView(Types); // adcrst2 -> moved from xaml-codebehind
            }
        }

        #endregion (Properties)

        #region Ctor

        internal TypeSelectionViewModel(MainViewModel mainViewModel)
        {
            _mainViewModel = mainViewModel;

            // initialize commands...

            ExpandCollapseAllTypesCmd = new ExpandCollapseAllTypesCmd(this);
            TypeCollectionView.Filter = TypeFilter;

            //TODO:
            IsCommandHandling = false;
        }

        #endregion (Ctor)

        #region Methods

        public void AddType(AutomationObjType automationObjType)
        {
            AutomationObjectBase addedType;

            if (automationObjType == AutomationObjType.CMT)
            {
                CMTCollectionObj cmtCollectionObj = AutomationObjectFactory.AddChild(CMTContainer.Instance, AutomationObjType.CMTCollectionObj) as CMTCollectionObj;
                cmtCollectionObj.AddChild(AutomationObjType.CMTemplateObj); 
                addedType = cmtCollectionObj.MasterCMTemplate.CMBase;
            }
            else if (automationObjType == AutomationObjType.AggregatedCMTemplate)
            {
                CMTCollectionObj cmtCollectionObj = AutomationObjectFactory.AddChild(CMTContainer.Instance, AutomationObjType.CMTCollectionObj) as CMTCollectionObj;
                addedType = cmtCollectionObj.AddChild(AutomationObjType.AggregatedCMTemplate);
            }
            else if (automationObjType == AutomationObjType.EMT)
            {
                var emAggregator = AutomationObjectFactory.AddChild(EMTContainer.Instance, AutomationObjType.EMTemplateObj) as EMTemplateObj;
                addedType = emAggregator.EMBase;
            }
            else
            {
                AutomationObjectBase parent;
                if (automationObjType == AutomationObjType.EMT)
                    parent = EMTContainer.Instance;
                else if (automationObjType == AutomationObjType.Function)
                    parent = FunctionContainer.Instance;
                else
                    parent = EnumerationTypes.Instance;

                addedType = AutomationObjectFactory.AddChild(parent, automationObjType);
            }

            var newType = new ListViewItemViewModel(addedType, this);

            // ApplyDefaultValuesForAllAttributes and for all auto created childs
            newType.AutomationObjectBase.ApplyDefaultValuesForAllAttributesRecursive();

            // only Workaround:
            // ADBEMI15, Michael Berenz, 01.08.2013: implemented a Save() method for Framework. moved Ivans change to this method.
            //      Ivan P, F37771, 15.07.2013: Save EMT after create to avoid inconsistency when TypeConfigurator
            //      is closed without saving (Comment Michael B.: triggered by the implementation of the sequence chain. TODO: try to find better solution)
            if (newType.AutomationObjectBase is EMT ||
                newType.AutomationObjectBase is AggregatedCMTemplate ||
                newType.AutomationObjectBase is CMBase)
            {
                newType.AutomationObjectBase.Parent.Save();
                newType.AutomationObjectBase.Save();
            }
            Types.Add(newType);
            SelectedType = newType;
        }


        #endregion

    }//end TypesOverViewViewModel

现在,我们已经为扩展器控件实现了一个控件模板。 在该控制模块中,切换按钮的可见性绑定到转换器。 转换器的目的是仅当每个 listviewitemviewModel 中至少有一个 child 时才显示切换按钮。

同样只是一个粗略的代码,用于显示转换器如何用于显示 ResouceDictionary.xaml 文件中某处扩展器中切换按钮的可见性:

     <local:ExpanderVisibilityConverter x:Key="ExpanderVisibilityConverter" />



     <ControlTemplate x:Key="LazyExpanderTemplate" TargetType="Expander">
        <Border Name="OuterExpanderBorder" 
                CornerRadius="3" 
                BorderThickness="{TemplateBinding Border.BorderThickness}" 
                BorderBrush="{TemplateBinding Border.BorderBrush}" 
                Background="{TemplateBinding Panel.Background}" 
                SnapsToDevicePixels="True">
            <DockPanel>
                <!--<Border Name="headerBorder" Margin="0,0,0,1" CornerRadius="2" BorderThickness="1"-->
                <Border DockPanel.Dock="Top" CornerRadius="2" BorderThickness="1"
                                    Background="{TemplateBinding Background}" 
                                    BorderBrush="{TemplateBinding BorderBrush}"
                                    SnapsToDevicePixels="True"
                        HorizontalAlignment="Stretch">

                    <Border DockPanel.Dock="Top" Name="InnerBorder"
                            CornerRadius="1"
                            BorderThickness="1">
                        <!--BorderBrush="{TemplateBinding BorderBrush}">-->
                        <Grid DockPanel.Dock="Top">
                            <Grid.RowDefinitions>
                                <RowDefinition MaxHeight="11"/>
                                <RowDefinition/>
                            </Grid.RowDefinitions>
                            <Rectangle Name="UpperHighlight"
                                               Visibility="Collapsed"
                                               Fill="#75FFFFFF"/>

                            <DockPanel DockPanel.Dock="Top" Name="HeaderSite"
                                           Grid.RowSpan="2" >

                                    <ToggleButton 
                                            DockPanel.Dock="Right"
                                            IsChecked="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=IsExpanded}"
                                            Foreground="{TemplateBinding TextElement.Foreground}" 
                                            FontFamily="{TemplateBinding TextElement.FontFamily}" 
                                            FontSize="{TemplateBinding TextElement.FontSize}" 
                                            FontStretch="{TemplateBinding TextElement.FontStretch}" 
                                            FontStyle="{TemplateBinding TextElement.FontStyle}" 
                                            FontWeight="{TemplateBinding TextElement.FontWeight}" 
                                            HorizontalContentAlignment="{TemplateBinding Control.HorizontalContentAlignment}" 
                                            VerticalContentAlignment="{TemplateBinding Control.VerticalContentAlignment}" 
                                            Padding="{TemplateBinding Control.Padding}"
                                            Visibility="{Binding Path=., Converter={StaticResource ExpanderVisibilityConverter}}"
                                            MinWidth="0" 
                                            MinHeight="0" 
                                            Margin="1">
                                        <ToggleButton.Style>
                                            <Style TargetType="ToggleButton">
                                                <Setter Property="Control.Template">
                                                    <Setter.Value>
                                                        <ControlTemplate TargetType="ToggleButton">
                                                            <Border Padding="{TemplateBinding Control.Padding}">
                                                                <Grid Background="Transparent" SnapsToDevicePixels="False">
                                                                    <Grid.ColumnDefinitions>
                                                                        <ColumnDefinition Width="14" />
                                                                        <ColumnDefinition Width="*" />
                                                                    </Grid.ColumnDefinitions>
                                                                    <Ellipse Stroke="#FFA9A9A9" Name="circle" Width="14" Height="14" HorizontalAlignment="Center" VerticalAlignment="Center" />
                                                                    <Ellipse Name="shadow" Width="15" Height="15" HorizontalAlignment="Center" VerticalAlignment="Center" Visibility="Hidden" />
                                                                    <!--TODO: Muss das sein-->
                                                                    <Path Data="M1,1.5L4.5,5 8,1.5" Stroke="#FF666666" StrokeThickness="2" Name="arrow" HorizontalAlignment="Center" VerticalAlignment="Center" SnapsToDevicePixels="False" />
                                                                </Grid>
                                                            </Border>
                                                            <ControlTemplate.Triggers>
                                                                <Trigger Property="ToggleButton.IsChecked" Value="True" >
                                                                    <Setter Property="Path.Data" TargetName="arrow">
                                                                        <Setter.Value>
                                                                            <StreamGeometry>M1,4.5L4.5,1 8,4.5</StreamGeometry>
                                                                        </Setter.Value>
                                                                    </Setter>
                                                                </Trigger>
                                                                <Trigger Property="UIElement.IsMouseOver" Value="True">
                                                                    <Setter Property="Shape.Stroke" TargetName="circle">
                                                                        <Setter.Value>
                                                                            <SolidColorBrush>#FF666666</SolidColorBrush>
                                                                        </Setter.Value>
                                                                    </Setter>
                                                                    <Setter Property="Shape.Stroke" TargetName="arrow">
                                                                        <Setter.Value>
                                                                            <SolidColorBrush>#FF222222</SolidColorBrush>
                                                                        </Setter.Value>
                                                                    </Setter>
                                                                    <Setter Property="UIElement.Visibility" TargetName="shadow">
                                                                        <Setter.Value>
                                                                            <x:Static Member="Visibility.Visible" />
                                                                        </Setter.Value>
                                                                    </Setter>
                                                                </Trigger>
                                                            </ControlTemplate.Triggers>
                                                        </ControlTemplate>
                                                    </Setter.Value>
                                                </Setter>
                                            </Style>
                                        </ToggleButton.Style>
                                        <ToggleButton.FocusVisualStyle>
                                            <Style TargetType="IFrameworkInputElement">
                                                <Setter Property="Control.Template">
                                                    <Setter.Value>
                                                        <ControlTemplate>
                                                            <Border>
                                                                <Rectangle Stroke="#FF000000" StrokeThickness="1" StrokeDashArray="1 2" Margin="0,0,0,0" SnapsToDevicePixels="True" />
                                                            </Border>
                                                        </ControlTemplate>
                                                    </Setter.Value>
                                                </Setter>
                                            </Style>
                                        </ToggleButton.FocusVisualStyle>
                                    </ToggleButton>                               
                                <ContentPresenter
                                    RecognizesAccessKey="True"
                                    Content="{TemplateBinding HeaderedContentControl.Header}" 
                                    ContentTemplate="{TemplateBinding HeaderedContentControl.HeaderTemplate}"
                                    ContentStringFormat="{TemplateBinding HeaderedContentControl.HeaderStringFormat}"
                                    Margin="4,0,0,0"
                                    HorizontalAlignment="Stretch"

                                    VerticalAlignment="Center"
                                    SnapsToDevicePixels="True"/>

                            </DockPanel>

                        </Grid>
                    </Border>
                </Border>
                <ContentPresenter Content="{TemplateBinding ContentControl.Content}" 
                                      ContentTemplate="{TemplateBinding ContentControl.ContentTemplate}" 
                                      ContentStringFormat="{TemplateBinding ContentControl.ContentStringFormat}" 
                                      Name="ExpandSite" 
                                      Margin="{TemplateBinding Control.Padding}" 
                                      HorizontalAlignment="{TemplateBinding Control.HorizontalContentAlignment}" 
                                      VerticalAlignment="{TemplateBinding Control.VerticalContentAlignment}" 
                                      Visibility="Collapsed" 
                                      Focusable="False" 
                                      DockPanel.Dock="Bottom" />
            </DockPanel>
        </Border>
</ControlTemplate>

ExpanderVisibilityConverter 是实现 IValueConverter interface.InConvert 方法的 class Convert 方法,每个 listViewItemVIewModel 都会检查其 child 元素,如果 child,return visibility.visible 否则 visibility.collapsed

问题是,如果我在应用程序 运行 后 运行 开始填充 "Types",并且我在 "Types" 中添加了第一个 listviewItemVIewModel,ExpanderVisibilityConverter.Convert 在我检查 listviewItemViewModel 是否有 child 的地方被调用,在这种情况下它没有任何 child,因此不会显示切换按钮。 现在我向 listviewItemViewModel 添加了一个 child,现在也没有显示切换按钮,因为没有为 child 元素调用 ExpanderVisibilityConverter.Convert 方法。

现在我希望切换按钮在我将 child 添加到 listViewItemViewModel 时立即可见。 请帮我实现这个目标。

我是如何实现转换器的。

public class ExpanderVisibilityConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            var tvItem = value as ListViewItemViewModel;
            if (tvItem != null && tvItem.Variants.Count > 0)
            {
                return Visibility.Visible;
            }
            return Visibility.Collapsed;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return Binding.DoNothing;
        }       
    }

我添加了实际 UI 部分的图像: 请看下图:

您必须对 ViewModel 进行一些更改。现在您的 Variants 属性 没有传播对有界项目 (ToggleButton) 的更改。所以, 我们需要在 ObservableCollection 更改时通知绑定机制,我们将使用 CollectionChanged 事件来完成此操作。

最后我们将 ToggleButton binding 更改为 {Binding Path=Variants, Converter={StaticResource ExpanderVisibilityConverter}}

class ListViewItemViewMOdel:INotifyPropertyChanged
{
        public string Name
        {
            return "SomeString";
        }
        private ObservableCollection<ListViewItemViewModel> _variants;

        public ObservableCollection<ListViewItemViewModel> Variants
        {
            get
            {
                return _variants;
            }
        }

        public ListViewItemViewMOdel()
        {
            _variants.CollectionChanged+=_variants_CollectionChanged;
        }

        void _variants_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            OnPropertyChanged("Variants");
        }

        public event PropertyChangedEventHandler PropertyChanged;
        private void OnPropertyChanged(string prop)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(prop));
        }
}

运行 上面的代码并告诉会发生什么?