4

I would like to remove them as this is causing me lots of problems. If there is a way to solve it I will be glad to try it. First few minutes of using it I have gotten like 3 different exceptions and I can't figure out how to remove those damn options.

Pinning and unpinning and pinning, throws an InvalidOperationException because the Operation is not valid due to the current state of the object.

Sometimes pinning and unpinning will open a dialog box and ask me for a file, I don't want that to happen but it is happening and it throws an exception.

I accidentally click the close button and I cannot get the window back. It is really frustrating. I am sure other avalondock users have come across this.

And as I don't want to waste too much time, I am going to ask right here. How did you get around this exceptions or remove those buttons? Thanks.

Lews Therin
  • 10,907
  • 4
  • 48
  • 72

4 Answers4

3

I had exactly the same problem as yours. Not willing to remove the icons from the actual UI, I just disabled them with event handlers

Here is how I worked:

To remove those Hide and AutoHide commands:

I added the following handler:

CommandManager.AddPreviewExecutedHandler(this, new ExecutedRoutedEventHandler(this.ContentClosing))

This is what ContentClosing looks like:

/// <summary>
/// Handler called when user clicked on one of the three buttons in a DockablePane
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void ContentClosing(object sender, ExecutedRoutedEventArgs e)
{
    if (e.Command == ApplicationCommands.Close || e.Command == DockablePaneCommands.Close)
    {
        e.Handled = true;
        DockManager source = sender as DockManager;
        if (source.ActiveContent != null)
        {
            source.ActiveContent.Close();
        }
    }
    else if (e.Command == DockablePaneCommands.Hide || e.Command == DockablePaneCommands.ToggleAutoHide)
    {
        e.Handled = true;

} }

This handler was here to actually close the right content. Fore some reason, sometimes AvalonDock will close another content because it has the focus (clicking on the cross won't give focus to your content, and thus it will close the currently focused content...) As you can see, I just override the events and close manually my components

Unfortunately, this does not cover all the cases. I also had for some reason (hello buggy AvalonDock) to actually catch the click on the close button because there is an edge case: If you remove the last component, you won't be able to add a new component, because AvalonDock will remove the last remaining panel. Moreover, if you close a DockableContent with many tabs in it, AvalonDock will close all the tabs, so I had to implement something to just close the current tab (which makes more sense imho) I had to add a mouse down handler to each content added to catch this event. With the following trick, I could do a workaround to avoid this bug:

    /// <summary>
    /// Handler called when a DockableContent state changed.
    /// We need it to define a PreviewMouseDownHandler for each DockablePane
    /// possibly created (which are usually created upon docking a floating window
    /// to a new position) in order to handle single DockableContent closing
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    void NewContent_StateChanged(object sender, RoutedEventArgs e)
    {
        DockableContent source = sender as DockableContent;
        if (source.State == DockableContentState.Docked && source.Parent is DockablePane)
        {
            DockablePane parent = source.Parent as DockablePane;
            parent.PreviewMouseDown -= mouseHandler;
            parent.PreviewMouseDown += mouseHandler;
        }
    }

    /// <summary>
    /// Handler called on mouse down on a DockablePane.
    /// It is designed to detect where did the user click, and 
    /// if he clicked on Close, only the current DockableContent will be closed
    /// (unlike the native behavior which requires us to close the entire DockablePane
    /// upon clicking on Close...)
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    void DockablePaneMouseDown(object sender, MouseButtonEventArgs e)
    {
        DockablePane source = sender as DockablePane;
        if (e.OriginalSource is AvalonDock.ImageEx)
        {
            //User clicked on one of the three icons on the top-right corner of the DockablePane
            if ((e.OriginalSource as AvalonDock.ImageEx).Source.ToString().Contains("PinClose"))
            {
                RemoveFromUI(source.SelectedItem as DockableContent);
                e.Handled = true;
            }
        }
    }

    /// <summary>
    /// Removes a DockableContent from the currently displayed UI
    /// (called when the original DockableItemsSource changed)
    /// </summary>
    /// <param name="content">The content to be removed</param>
    private void RemoveFromUI(DockableContent content)
    {
        if (content == null)
        {
            return;
        }
        DockablePane parent = content.Parent as DockablePane;
        if (this.ActiveContent == parent.SelectedItem)
        {
            this.ActiveContent = null;
        }
        (parent.SelectedItem as DockableContent).Close();
        //// If the current DockablePane is left empty, we ensure to close it
        if (parent.Items.Count == 0)
        {
            //This case is needed if we are trying to remove the last DockablePane from a DockingManager
            //Native behavior will NOT set the Content property if you remove the last DockablePane:
            //it will therefore consider this CLOSED DockablePane as the current ActiveContent,
            //and will try to add new contents in this closed pane, which seems rather disturbing.
            //Here we explicitly set the Content property to null if we are removing the last element,
            //so next time user adds a tab, it will be added as the new Content!
            if (parent == this.Content)
            {
                this.Content = null;
            }
            parent.Close();
            parent.Visibility = Visibility.Hidden;
        }
    }

Again, this is an incredible big work for such small matters, but unfortunately, this AvalonDock is far from being production-environment ready and I had to tweak such things to make it work.

Hope it'll work for you as well and save yourself some headaches I've already given to this problem!

Damascus
  • 6,553
  • 5
  • 39
  • 53
  • Thanks, I will try this at work and hopefully it works (accepted and upvoted if it does of course :P) . – Lews Therin Aug 16 '12 at 20:45
  • 1
    As a matter of fact, I included this dock with the tweaks listed above in our application, it's deployed and nobody has ever told me they had a bug with it for around 1 year, so I'm pretty confident =) – Damascus Aug 16 '12 at 21:06
  • Thank you Damascus. And I like your confidence :P – Lews Therin Aug 17 '12 at 12:02
3

If you are using the MVVM approach to Avalon Dock (version 2) then you can put this in your view model:

DockAsDocumentCommand = new DelegateCommand(() => { }, () => false);
AutoHideCommand = new DelegateCommand(() => { }, () => false);
CanClose = false;
CanHide = false;

Those all need to have TwoWay bindings and NotifyPropertyChanged on them.

Once you do that all the options for closing, hiding and moving to another document will be removed or grayed out.

Vaccano
  • 78,325
  • 149
  • 468
  • 850
2

If you use MVVM, setting it CanClose to false in XAML is sufficient,like this:

        <avalondock:DockingManager.LayoutItemContainerStyleSelector>
            <avalon:PanesStyleSelector>
                <avalon:PanesStyleSelector.DeviceStyle>
                    <Style TargetType="{x:Type avalondock:LayoutItem}">
                        <Setter Property="Title" Value="{Binding Model.Name}"/>
                        <Setter Property="ToolTip" Value="{Binding Model.Name}"/>
                        <Setter Property="ContentId" Value="{Binding Model.Id}"/>
                        <Setter Property="CanClose" Value="False"></Setter>
                    </Style>
                </avalon:PanesStyleSelector.DeviceStyle>
            </avalon:PanesStyleSelector>
        </avalondock:DockingManager.LayoutItemContainerStyleSelector>
0

Another way to get rid of Close, CloseAll and CloseAllButThis commands is to set their commands to null in LayoutItemContainerStyleSelector. Something like:

<xcad:DockingManager.LayoutItemContainerStyleSelector>
    <local:PanesStyleSelector>
      <local:PanesStyleSelector.DocStyle>
          <Setter Property="CloseCommand" Value="{Binding Model.CloseCommand}"/>
          <Setter Property="CloseAllCommand" Value="{x:Null}" />
          <Setter Property="CloseAllButThisCommand" Value="{x:Null}" />
        </Style>
      </local:PanesStyleSelector.DrawingStyle>
    </local:PanesStyleSelector>
  </xcad:DockingManager.LayoutItemContainerStyleSelector>

PanesStyleSelector is a simple StyleSelector (I had multiple styles to select from depending upon the pane type, so I needed a StyleSelector; you may want to skip it if you have only one type of pane. Following is the simplified version.):

Public Class PanesStyleSelector
  Inherits StyleSelector

  Public Property DocStyle() As Style

  Public Overrides Function SelectStyle(item As Object, container As System.Windows.DependencyObject) As System.Windows.Style
    Return DocStyle
  End Function
End Class

This will disable both CloseAll and CloseAllButThis commands in the context menu of the document tabs. Also note that I'm handling CloseCommand in my VM, which I can decide whether to close my document or maybe prompt user about it. This will get rid of the accidental clicking of the close button.

dotNET
  • 33,414
  • 24
  • 162
  • 251