【问题标题】:AvalonDock:绑定到 LayoutAnchorablePane 位置?
【发布时间】:2022-01-17 23:57:26
【问题描述】:

我正在为使用 AvalonDock 对接框架的 WPF 应用程序创建自定义主题。
我已经在 AvalonDock 存储库上为我的问题打开了GitHub issue,但我希望我能在这里更快地得到答案(并准备尽快为此提供赏金)。


在我的自定义主题中,我将LayoutAnchorablePane 的选项卡项移动到左侧垂直堆叠,并且窗格使用列大小为Auto, *, Auto 的网格。
LayoutAnchorablePane 附加到根布局面板的右侧时,我想为将选项卡从左列移动到右列的样式编写一个触发器。 (这样标签总是在外面)

这是我尝试将触发器放在我的主题的 XAML 的相关部分。这与 AvalonDock 中 generic.xaml 样式的 LayoutAnchorablePaneControl 模板几乎相同:

<Grid
    ClipToBounds="true"
    KeyboardNavigation.TabNavigation="Local"
    SnapsToDevicePixels="true">
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="Auto" />
    <ColumnDefinition Width="*" />
    <ColumnDefinition Width="Auto" />
    </Grid.ColumnDefinitions>
    <!--  Following border is required to catch mouse events  -->
    <Border Grid.ColumnSpan="3" Background="Transparent" />
    <StackPanel
    x:Name="HeaderPanel"
    Width="40"
    Grid.Column="0"
    Panel.ZIndex="1"
    IsItemsHost="true"
    KeyboardNavigation.TabIndex="1" />
    <Border
    x:Name="ContentPanel"
    Grid.Column="1"
    Background="Transparent"
    BorderThickness="2"
    BorderBrush="{StaticResource PrimaryBrush}"
    KeyboardNavigation.DirectionalNavigation="Contained"
    KeyboardNavigation.TabIndex="2"
    KeyboardNavigation.TabNavigation="Cycle">
    <ContentPresenter
            x:Name="PART_SelectedContentHost"
            ContentSource="SelectedContent"
            SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
    </Border>
</Grid>
<ControlTemplate.Triggers>
    <DataTrigger Binding="{Binding ??? }">
        <Setter TargetName="HeaderPanel" Property="Grid.Column" Value="2"/>
    </DataTrigger>
</ControlTemplate.Triggers>

据我所知,LayoutAnchorablePane 或其任何接口上没有属性可以显示窗格位于布局的哪一侧。所以我迷失了我可以在我的 DataTrigger 中输入 {Binding ??? } 的内容。

看来我需要自己实现该属性并使用我自己构建的 AvalonDock。如果可能的话,我想避免这种情况;所以也许我可以在自己的代码中实现一些聪明的 MarkupExtension 或 Converter 想法?也许我认为这可以通过DataTrigger 完成的假设也可能受到挑战。我很乐意为此使用完全代码隐藏的解决方案。

【问题讨论】:

    标签: c# wpf avalondock


    【解决方案1】:

    感谢 BionicCode 提供了非常详细的答案,我最终只需要从该答案中获得一些提示即可以我自己的方式解决问题。所以我认为也值得分享我的代码。


    LayoutAnchorablePaneControl 继承自 TabControl,因此它已经拥有 TabStripPlacement 属性,样式可以绑定到其模板化父级。

    所以新样式将Grid 替换为DockPanel,如下所示:

    <DockPanel
        ClipToBounds="true"
        KeyboardNavigation.TabNavigation="Local"
        SnapsToDevicePixels="true">
        <!--  Following border is required to catch mouse events  -->
        <Border Background="Transparent" />
        <StackPanel
        x:Name="HeaderPanel"
        Width="40"
        DockPanel.Dock="{TemplateBinding TapStripPlacement}"
        Panel.ZIndex="1"
        IsItemsHost="true"
        KeyboardNavigation.TabIndex="1" />
        <Border
        x:Name="ContentPanel"
        Background="Transparent"
        BorderThickness="2"
        BorderBrush="{StaticResource PrimaryBrush}"
        KeyboardNavigation.DirectionalNavigation="Contained"
        KeyboardNavigation.TabIndex="2"
        KeyboardNavigation.TabNavigation="Cycle">
        <ContentPresenter
                x:Name="PART_SelectedContentHost"
                ContentSource="SelectedContent"
                SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
        </Border>
    </DockPanel>
    

    现在,此样式将根据 LayoutAnchorablePaneControl.TabStripPlacement 属性将选项卡移动到任意一侧(左/右/上/下)。

    在后面的代码中(对于具有 DockingManager 的窗口),我将一个事件处理程序附加到运行以下方法的 DockingManager.Layout.Updated

    private void UpdateTabSides()
    {
        foreach (LayoutAnchorablePaneControl apc in DockManager.LayoutRootPanel.FindLogicalChildren<LayoutAnchorablePaneControl>())
        {
            var side = apc.Model.GetSide();
            if (side == AnchorSide.Right)
            {
                apc.TabStripPlacement = Dock.Right;
            }
            else
            {
                apc.TabStripPlacement = Dock.Left;
            }
        }
    }
    

    我发现这种方法比 BionicCode 的答案简单得多,但他们应该得到奖励,将我推向正确的方向。

    【讨论】:

      【解决方案2】:

      AvalonDock 的实现非常奇怪。我记得当我不得不在项目中使用这个控件时也遇到了一些麻烦。我想我很了解这个控制。在我看来,它的实施非常糟糕。因为出于某种原因,他们决定使用 MVVM 自己实现此控件,而不是简单地使其准备好 MVVM。这使得在高级场景中使用非常不方便。
      语义也很混乱。例如,容器不是呈现数据的控件。容器被分配给控件的Model 属性。数据未分配给DataContext。不过看起来不错。

      此外,选项卡标题放置行为被破坏(仅允许底部的选项卡标题)。我的修复可能会让您感兴趣,尤其是在动态选项卡标题放置的上下文中。请参阅下面的 Style 以获取预期的选项卡标题放置行为。它只是将LayoutAnchorablePaneControl 的内容包装到DockingPanel 中并旋转标题主机,以便您在Visual Studio 中获得标签标题对齐(按宽度堆叠)。就这样。如果您希望按高度堆叠标题(不旋转),只需将 AnchorablePaneTabPanel 替换为 StackPanel 并移除旋转触发器。
      提供的示例基于下面的Style。否则,您将无法将选项卡标题位置传播到视图。

      另一个大问题是缺少 DockingManager 类和 AvalonDock 公开的事件。这意味着没有机会观察拖放操作。事实上,DockingManager 只暴露了三个相当无趣的事件。与 LayoutAnchorablePaneControl 等内容主机相同。
      由于 AvalonDock 不使用 WPF 框架的拖放 API,因此处理这些事件不是解决方案。

      要克服这些缺点,您必须处理少数模型事件之一,在本例中为 LayoutRoot.Updated 事件。

      该解决方案仅针对LayoutAnchorablePaneLayoutAnchorableGroupPane。要解决高级分组或LayoutDocumentPane,您可以按照模式简单地扩展示例。
      由于您只需要/请求两列布局,因此该算法将完成这项工作。支持其他更高级的布局安排,但行为并不完美,因为当前并未跟踪所有条件。重点是两列布局。这是一个快速(但不那么脏)且非常简单的解决方案。
      您应该考虑明确禁止除两列布局之外的任何布局安排。

      此外,AvalonDock 不提供事件来指示视觉布局过程何时完成。当布局模型添加到布局模型树/从布局模型树中删除时,您只会通过LayoutRoot.Updated 事件收到通知。但是您永远不知道可视化树的确切更新时间。我们需要访问可视化容器,以便根据该控件的新位置设置LayoutPanelControl.TabStripPlacement 属性。
      为了克服这个问题,我使用Dispatcher 来推迟对然后初始化和渲染的LayoutAnchorablePaneControl 的访问。否则选项卡标题的排列会为时过早,因为控件的布局索引尚未更改。 AvalonDock 只允许跟踪极少的布局模型修改,但根本无法观察实际的对接操作。

      所以算法基本上是

      1. 处理 LayoutRoot.Updated 事件并启动使用 Dispatcher 延迟的实际定位算法
      2. 遍历所有窗格控件以更新选项卡标题位置。如果允许嵌套,您将拥有一个必须递归遍历的布局树,就像在本示例中对组窗格所做的那样。
      3. 根据索引确定布局中窗格的位置。
      4. 根据索引设置LayoutPanelControl.TabStripPlacement属性:索引为0表示左,等于项数的索引表示右。其他所有索引都介于两者之间。选项卡标题是根据窗格在布局中的位置放置的。
      5. DockingPanel 将相应地布局选项卡项。如果选项卡标题位于左侧或右侧,则触发器用于旋转它们。

      布局中可以有多个LayoutPanelControl 元素(除非您不允许“非法”布局安排来强制执行两列布局)。

      MainWindow.xaml.cs

      public partial class MainWindow : Window
      {
        private const Dock DefaultDockPosition = Dock.Bottom;
      
        private void InitializeOnDockingManager_Loaded(object sender, RoutedEventArgs e)
        {
          var dockingManager = sender as DockingManager;
          this.Dispatcher.InvokeAsync(() =>
          {
            ArrangePanel(dockingManager.LayoutRootPanel);
          },
          DispatcherPriority.Background);
      
          dockingManager.Layout.Updated += OnLayoutUpdated;
        }
      
        private void OnLayoutUpdated(object sender, EventArgs e)
        {
          var layoutRoot = sender as LayoutRoot;
          var dockingManager = layoutRoot.Manager;
          this.Dispatcher.InvokeAsync(() =>
          {
            ArrangePanel(dockingManager.LayoutRootPanel);
          },
          DispatcherPriority.ContextIdle);
        }
      
        private void ArrangePanel(LayoutPanelControl layoutPanelControl)
        {
          IEnumerable<ILayoutControl> layoutControls = layoutPanelControl.Children
            .OfType<ILayoutControl>()
            .Where(control =>
              control is LayoutAnchorablePaneControl paneControl
                && (paneControl.Model as ILayoutContainer).Children.Any()
              || control is LayoutAnchorablePaneGroupControl or LayoutPanelControl);
      
          int paneControlCount = layoutControls.Count(control => control is not LayoutPanelControl);
          int paneControlLayoutPosition = 0;
      
          foreach (ILayoutControl layoutControl in layoutControls)
          {
            if (layoutControl is LayoutPanelControl layoutPanel)
            {
              ArrangePanel(layoutPanel);
              continue;
            }
      
            paneControlLayoutPosition++;
            bool isFirst = paneControlLayoutPosition == 1;
            bool isLast = paneControlCount == paneControlLayoutPosition;
      
            if (layoutControl is LayoutAnchorablePaneGroupControl paneGroupControl)
            {
              PositiontabHeadersInPaneGroup((isFirst, isLast), paneGroupControl);
            }
            else if (layoutControl is LayoutAnchorablePaneControl paneControl)
            {
              if (paneControlCount == 1)
              {
                paneControl.TabStripPlacement = DefaultDockPosition;
              }
              else
              {
                PositionTabHeadersInPane(paneControl, isFirst, isLast);
              }
            }
          }
        }
      
        private static void PositionTabHeadersInPane(LayoutAnchorablePaneControl paneControl, bool isFirst, bool isLast)
          => paneControl.TabStripPlacement =
            (isFirst, isLast) switch
            {
              (true, _) => Dock.Left,
              (_, true) => Dock.Right,
              _ => DefaultDockPosition
            };
      
        private void PositiontabHeadersInPaneGroup((bool IsGroupFirst, bool IsGroupLast) parentPaneGroupPosition, LayoutAnchorablePaneGroupControl paneGroupControl)
        {
          IEnumerable<ILayoutControl> groupMembers = paneGroupControl.Children
            .OfType<ILayoutControl>();
          int groupMemberCount = groupMembers.Count();
          int layoutPosition = 0;
      
          foreach (ILayoutControl groupMember in groupMembers)
          {
            layoutPosition++;
            bool isFirst = layoutPosition == 1;
            bool isLast = layoutPosition == groupMemberCount;
      
            if (groupMember is LayoutAnchorablePaneGroupControl childGroupControl)
            {
              PositiontabHeadersInPaneGroup((isFirst, isLast), childGroupControl);
            }
            else if (groupMember is LayoutAnchorablePaneControl paneControl)
            {
              (bool IsPaneFirstInGroup, bool IsPaneLastInGroup) panePositionInGroup = (isFirst, isLast);
      
              paneControl.TabStripPlacement =
                !parentPaneGroupPosition.IsGroupFirst && !parentPaneGroupPosition.IsGroupLast
                || groupMemberCount == 1
                  ? DefaultDockPosition
                  : (parentPaneGroupPosition, panePositionInGroup, paneGroupControl.Orientation) switch
                  {
                    ({ IsGroupFirst: true }, { IsPaneFirstInGroup: true }, Orientation.Horizontal) => Dock.Left,
                    ({ IsGroupLast: true }, { IsPaneLastInGroup: true }, Orientation.Horizontal) => Dock.Right,
                    ({ IsGroupFirst: true }, _, Orientation.Vertical) => Dock.Left,
                    ({ IsGroupLast: true }, _, Orientation.Vertical) => Dock.Right,
                    _ => DefaultDockPosition
                  };
            }
          }
        }
      }
      

      MainWindow.xaml
      所需的AnchorablePaneControlStyle 定义如下。

      <xcad:DockingManager Loaded="InitializeOnDockingManager_Loaded"
                           AnchorablePaneControlStyle="{StaticResource AnchorablePaneControlStyle}"
                           Height="500"
                           Width="500"
                           HorizontalAlignment="Left">
        <xcad:LayoutRoot>
          <xcad:LayoutPanel Orientation="Horizontal">
            <xcad:LayoutAnchorablePane>
              <xcad:LayoutAnchorable ContentId="properties"
                                     Title="Properties">
                <TextBlock Text="123abc" />
              </xcad:LayoutAnchorable>
              <xcad:LayoutAnchorable Title="AgendaLeft"
                                     ContentId="agendaLeft">
                <TextBlock Text="Agenda Content" />
              </xcad:LayoutAnchorable>
              <xcad:LayoutAnchorable Title="ContactsLeft"
                                     ContentId="contactsLeft">
                <TextBlock Text="Contacts Content" />
              </xcad:LayoutAnchorable>
            </xcad:LayoutAnchorablePane>
          </xcad:LayoutPanel>
        </xcad:LayoutRoot>
      </xcad:DockingManager>
      

      AnchorablePaneControlStyle

      <Style x:Key="AnchorablePaneControlStyle"
             TargetType="{x:Type xcad:LayoutAnchorablePaneControl}">
        <Setter Property="Foreground"
                Value="{Binding Model.Root.Manager.Foreground, RelativeSource={RelativeSource Self}}" />
        <Setter Property="Background"
                Value="{Binding Model.Root.Manager.Background, RelativeSource={RelativeSource Self}}" />
        <Setter Property="TabStripPlacement"
                Value="Bottom" />
        <Setter Property="Template">
          <Setter.Value>
            <ControlTemplate TargetType="{x:Type xcad:LayoutAnchorablePaneControl}">
              <Grid ClipToBounds="true"
                    SnapsToDevicePixels="true"
                    KeyboardNavigation.TabNavigation="Local">
                
                <!--Following border is required to catch mouse events-->
                <Border Background="Transparent"
                        Grid.RowSpan="2" />
                
                <DockPanel>
                  <xcad:AnchorablePaneTabPanel x:Name="HeaderPanel"
                                               DockPanel.Dock="{TemplateBinding TabStripPlacement}"
                                               Margin="2,0,2,2"
                                               IsItemsHost="true"
                                               KeyboardNavigation.TabIndex="1"
                                               KeyboardNavigation.DirectionalNavigation="Cycle">
                    <xcad:AnchorablePaneTabPanel.LayoutTransform>
                      <RotateTransform x:Name="TabPanelRotateTransform" />
                    </xcad:AnchorablePaneTabPanel.LayoutTransform>
                  </xcad:AnchorablePaneTabPanel>
                  
                <Border x:Name="ContentPanel" DockPanel.Dock="{TemplateBinding TabStripPlacement}"
                          BorderBrush="{TemplateBinding BorderBrush}"
                          BorderThickness="{TemplateBinding BorderThickness}"
                          KeyboardNavigation.DirectionalNavigation="Contained"
                          KeyboardNavigation.TabIndex="2"
                          KeyboardNavigation.TabNavigation="Cycle">
                    <ContentPresenter x:Name="PART_SelectedContentHost"                                    
                                      ContentSource="SelectedContent"
                                      Margin="{TemplateBinding Padding}"
                                      SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
                  </Border>
                </DockPanel>
              </Grid>
              
              <ControlTemplate.Triggers>
                <Trigger Property="TabStripPlacement"
                         Value="Top">
                  <Trigger.EnterActions>
                    <StopStoryboard BeginStoryboardName="LeftTabStripPlacementAnimation" />
                    <StopStoryboard BeginStoryboardName="RightTabStripPlacementAnimation" />
                    <StopStoryboard BeginStoryboardName="BottomTabStripPlacementAnimation" />
                    <BeginStoryboard x:Name="TopTabStripPlacementAnimation">
                      <Storyboard>
                        <DoubleAnimation Storyboard.TargetName="TabPanelRotateTransform"
                                         Storyboard.TargetProperty="Angle"
                                         To="90"
                                         Duration="0" />
                      </Storyboard>
                    </BeginStoryboard>
                  </Trigger.EnterActions>
                </Trigger>
                <Trigger Property="TabStripPlacement"
                         Value="Bottom">
                  <Trigger.EnterActions>
                    <StopStoryboard BeginStoryboardName="LeftTabStripPlacementAnimation" />
                    <StopStoryboard BeginStoryboardName="TopTabStripPlacementAnimation" />
                    <StopStoryboard BeginStoryboardName="RightTabStripPlacementAnimation" />
                    <BeginStoryboard x:Name="BottomTabStripPlacementAnimation">
                      <Storyboard>
                        <DoubleAnimation Storyboard.TargetName="TabPanelRotateTransform"
                                         Storyboard.TargetProperty="Angle"
                                         To="0"
                                         Duration="0" />
                      </Storyboard>
                    </BeginStoryboard>
                  </Trigger.EnterActions>
                </Trigger>
                <Trigger Property="TabStripPlacement"
                         Value="Left">
                  <Trigger.EnterActions>
                    <StopStoryboard BeginStoryboardName="TopTabStripPlacementAnimation" />
                    <StopStoryboard BeginStoryboardName="RightTabStripPlacementAnimation" />
                    <StopStoryboard BeginStoryboardName="BottomTabStripPlacementAnimation" />
                    <BeginStoryboard x:Name="LeftTabStripPlacementAnimation">
                      <Storyboard>
                        <DoubleAnimation Storyboard.TargetName="TabPanelRotateTransform"
                                         Storyboard.TargetProperty="Angle"
                                         To="90" 
                                         Duration="0" />
                      </Storyboard>
                    </BeginStoryboard>
                  </Trigger.EnterActions>
                </Trigger>
                <Trigger Property="TabStripPlacement"
                         Value="Right">
                  <Trigger.EnterActions>
                    <StopStoryboard BeginStoryboardName="LeftTabStripPlacementAnimation" />
                    <StopStoryboard BeginStoryboardName="TopTabStripPlacementAnimation" />
                    <StopStoryboard BeginStoryboardName="BottomTabStripPlacementAnimation" />
                    <BeginStoryboard x:Name="RightTabStripPlacementAnimation">
                      <Storyboard>
                        <DoubleAnimation Storyboard.TargetName="TabPanelRotateTransform"
                                         Storyboard.TargetProperty="Angle"
                                         To="90"
                                         Duration="0" />
                      </Storyboard>
                    </BeginStoryboard>
                  </Trigger.EnterActions>
                </Trigger>
              </ControlTemplate.Triggers>
            </ControlTemplate>
          </Setter.Value>
        </Setter>
      
        <Setter Property="ItemContainerStyle">
          <Setter.Value>
            <Style TargetType="{x:Type TabItem}">
              <Setter Property="IsSelected"
                      Value="{Binding IsSelected, Mode=TwoWay}" />
              <Setter Property="IsEnabled"
                      Value="{Binding IsEnabled}" />
              <Setter Property="ToolTip"
                      Value="{Binding ToolTip}" />
              <Setter Property="Template">
                <Setter.Value>
                  <ControlTemplate TargetType="{x:Type TabItem}">
                    <Grid SnapsToDevicePixels="true">
                      <Border x:Name="Bd"
                              BorderBrush="{TemplateBinding BorderBrush}"
                              BorderThickness="1,0,1,1"
                              Background="{TemplateBinding Background}">
                        <ContentPresenter x:Name="Content"
                                          ContentSource="Header"
                                          HorizontalAlignment="{Binding HorizontalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"
                                          VerticalAlignment="{Binding VerticalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"
                                          RecognizesAccessKey="True"
                                          SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
                      </Border>
                    </Grid>
                    <ControlTemplate.Triggers>
                      <Trigger Property="Selector.IsSelected"
                               Value="true">
                        <Setter Property="Background"
                                Value="White" />
                        <Setter Property="Panel.ZIndex"
                                Value="1" />
                        <Setter Property="Margin"
                                Value="0,-1,-1,-2" />
                      </Trigger>
                      <MultiTrigger>
                        <MultiTrigger.Conditions>
                          <Condition Property="IsMouseOver"
                                     Value="true" />
                          <Condition Property="Selector.IsSelected"
                                     Value="false" />
                        </MultiTrigger.Conditions>
                        <Setter Property="Background"
                                Value="{DynamicResource {x:Static SystemColors.GradientInactiveCaptionBrushKey}}" />
                        <Setter Property="BorderBrush"
                                Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}" />
                        <Setter Property="Panel.ZIndex"
                                Value="0" />
                      </MultiTrigger>
                      <Trigger Property="IsEnabled"
                               Value="false">
                        <Setter Property="Foreground"
                                Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}" />
                      </Trigger>
                    </ControlTemplate.Triggers>
                  </ControlTemplate>
                </Setter.Value>
              </Setter>
              <Style.Triggers>
                <DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type TabControl}}, Path=Items.Count, FallbackValue=1}"
                             Value="1">
                  <Setter Property="Visibility"
                          Value="Collapsed" />
                </DataTrigger>
              </Style.Triggers>
            </Style>
          </Setter.Value>
        </Setter>
      
        <Setter Property="ItemTemplate">
          <Setter.Value>
            <DataTemplate>
              <xcad:LayoutAnchorableTabItem Model="{Binding}" />
            </DataTemplate>
          </Setter.Value>
        </Setter>
      
        <Setter Property="ContentTemplate"
                Value="{StaticResource AnchorablePaneControlContentTemplate}" />
      
      </Style>
      

      【讨论】:

      • 我没有意识到LayoutAnchorablePaneControl 是从TabControl 继承的。这意味着我可以绑定到TabStripPlacement 并从代码隐藏中控制它。您的回答涉及很多细节,但我最终只需要一件事来帮助我:来自Layout.Updated 的异步调用以给可视化树时间更新。
      • 是的,它们扩展了 TabControl。但是TabStripPlacement 坏了。这就是我在Style 中使用DockingPanel 作为面板主机的原因。原始标签项布局仅支持Dock.Bottom(我猜是偶然的)。控制似乎没有经过很好的测试。也没有看到任何 uint 测试。但它是免费的。也许付费版本可以解决一些问题。
      • 我上面提到的分叉没有付费版本。这是 Dirkster.AvalonDock,不是 Xceed WPF 工具包
      • 通用主题也负责“破坏”TapStripPlacement。它们不提供绑定到属性的模板,因此它只是完全未使用,而选项卡由于模板的Grid 而手动定位在底部。在我的回答中,我也使用了DockPanel 来重新连接到该属性。我敢打赌默认的 WPF 控件模板也会这样做。
      • 我很高兴任何可以在没有此类调查的情况下使用的库。这就是我所说的质量标准:)
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-02-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-10-29
      • 1970-01-01
      相关资源
      最近更新 更多