Changing Content (XAML) of DockablePane after Registration from External Command

Changing Content (XAML) of DockablePane after Registration from External Command

DomiKlotz
Explorer Explorer
856 Views
6 Replies
Message 1 of 7

Changing Content (XAML) of DockablePane after Registration from External Command

DomiKlotz
Explorer
Explorer

Hi,

 

I am trying to show (and edit) relevant (project specific)  Revit Element Parameters in a DockablePane.

 

I had a look at The Building Coder: A Simpler Dockable Panel Sample (typepad.com) and many other resources and was able to register the dockable Pane and have a Button to Show/Hide it (CodeSample [1]). 

 

Next Step i tried to Edit the Content of the XAML File before Registering the Dockable Pane which also worked (Line 52-53 CodeSample[2])

 

Now i want to access/edit the XAML File from a different External Command ( UpdateDockpane CodeSample [3] ) but I cant figure out how to access it.

 

Is my approach wrong or do I miss something?

 

There are more challenges for me ahead...

- automatically updating DockpaneContent when selection is changed

- write ParameterValues back to Revit Selected Elements from UserInput in DockablePane

which i will take care after clearing this problem...

 

Any help will be appreciated.

 

Best Regards,

Dominik

 

Code Samples:

[1] Show/Hide DockablePane:

namespace ILF_Parameters.core
{
    using Autodesk.Revit.UI;
    using Autodesk.Revit.DB;

    /// <summary>
    /// Schow ILF Parameters dokckable pane.
    /// </summary>
    /// <seealso cref="Autodesk.Revit.UI.IExternalCommand"/>
    [Autodesk.Revit.Attributes.Transaction(Autodesk.Revit.Attributes.TransactionMode.Manual)]
    [Autodesk.Revit.Attributes.Regeneration(Autodesk.Revit.Attributes.RegenerationOption.Manual)]
    public class ShowILFParametersPaneCommand : IExternalCommand
    {


        public bool dpIsVisible;


        #region public methods
        /// <summary>
        /// Executes the specified command data.
        /// </summary>
        /// <param name="commandData"></param>
        /// <param name="message"></param>
        /// <param name="elements"></param>
        /// <returns></returns>
        public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)
        {
            var dpid = new DockablePaneId(PaneIdentifiers.Get_ILF_Parameters_PaneIdentifier());
            var dp = commandData.Application.GetDockablePane(dpid);

            if (dp.IsShown()){
                dp.Hide();
            }
            else
            {
                dp.Show();
            }

            return Result.Succeeded;
        }

        

        /// <summary>
        /// Gets the full namespace path to this command.
        /// </summary>
        /// <returns></returns>
        public static string GetPath()
        {
            // Return constructed namespace path.
            return typeof(ShowILFParametersPaneCommand).Namespace + "." + nameof(ShowILFParametersPaneCommand);
        }

        #endregion
    }
}

 

[2]  Register the Dockable Pane

namespace ILF_Parameters.ui
{
    using System.Windows;
    using Autodesk.Revit.UI;
    using Autodesk.Revit.DB;
    using System.Threading.Tasks;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;

    using core;
    using Autodesk.Revit.UI.Events;
    using ILF_Parameters;
    using System.Windows.Controls;




    /// <summary>
    /// Register ILF Parameters Dockable .
    /// </summary>
    /// <seealso cref="Autodesk.Revit.UI.IExternalCommand"/>
    [Autodesk.Revit.Attributes.Transaction(Autodesk.Revit.Attributes.TransactionMode.Manual)]
    [Autodesk.Revit.Attributes.Regeneration(Autodesk.Revit.Attributes.RegenerationOption.Manual)]
    public class Register_ILF_Parameters_Pane_Command : IExternalCommand
    {
        
        #region public methods

        /// <summary>
        /// Executes the specified command data.
        /// </summary>
        /// <param name="commandData"></param>
        /// <param name="message"></param>
        /// <param name="elements"></param>
        /// <returns></returns>
        public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)
        {
            return Execute(commandData.Application);
        }

        /// <summary>
        /// Register dockable pane.
        /// </summary>
        /// <param name="uIApplication"></param>
        /// <returns></returns>
        public Result Execute(UIApplication uIApplication)
        {
            var data = new DockablePaneProviderData();
            var ILFParametersPage = new ILFParametersMainPage();

            TextBlock txtwhatchthis = (TextBlock) ILFParametersPage.FindName("MyTextBlock");
            txtwhatchthis.Text = txtwhatchthis.Text + " - OVERWRITTEN @Anonymous";

            data.FrameworkElement = ILFParametersPage as FrameworkElement;


            // Setup initial state.
            var state = new DockablePaneState
            {
                DockPosition = DockPosition.Right,
            };

            

            // Use unique guid identifier for this dockable pane.
            var dpid = new DockablePaneId(PaneIdentifiers.Get_ILF_Parameters_PaneIdentifier());

            uIApplication.RegisterDockablePane(dpid, "ILF Parameters", (IDockablePaneProvider)ILFParametersPage);

            
            return Result.Succeeded;
        }

        #endregion
    }
}

 

[3] UpdateDockpane Command:

namespace ILF_Parameters.core
{
    using Autodesk.Revit.DB;
    using Autodesk.Revit.UI;
    using Autodesk.Revit.UI.Selection;
    using System;
    using System.Collections.Generic;

    /// <summary>
    /// Command code to be executed when button is clicked.
    /// </summary>
    /// <seealso cref="Autodesk.Revit.UI.IexternalCommand"/>
    [Autodesk.Revit.Attributes.Transaction(Autodesk.Revit.Attributes.TransactionMode.Manual)]
    [Autodesk.Revit.Attributes.Regeneration(Autodesk.Revit.Attributes.RegenerationOption.Manual)]


    public class UpdateDockpane : IExternalCommand
    {
        #region public methods
        public Autodesk.Revit.UI.Result Execute(ExternalCommandData commandData,
        ref string message, ElementSet elements)
        {
            TaskDialog.Show("Revit", "TestyDoesMybuttonWork?");

            var dpid = new DockablePaneId(PaneIdentifiers.Get_ILF_Parameters_PaneIdentifier());
            var dp = commandData.Application.GetDockablePane(dpid);

            
            //How to edit XAML here?



            return Autodesk.Revit.UI.Result.Succeeded;

        }
        /// <summary>
        /// Gets the full namespace path to this command.
        /// </summary>
        /// <returns></returns>
        public static string GetPath()
        {
            // Return constructed namespace path.
            return typeof(UpdateDockpane).Namespace + "." + nameof(UpdateDockpane);
        }

        #endregion
    }
}

 [4] Xaml File

<Page x:Class="ILF_Parameters.ui.ILFParametersMainPage"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:ILF_Parameters.ui"
        mc:Ignorable="d"
        d:DesignHeight="300" d:DesignWidth="300"
        Title="ILF Parameters">

    
    <Grid Background="White" >
        <Grid.RowDefinitions>
            <RowDefinition Height="50" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>


        <Grid Grid.Row="0">
            <Grid.ColumnDefinitions >
                <ColumnDefinition Width="5" />
                <ColumnDefinition Width="*" />
                <ColumnDefinition Width="5" />
            </Grid.ColumnDefinitions>

            <TextBlock Margin ="0" Grid.Column="1" FontSize="12" Foreground="Black" TextWrapping="Wrap" FontFamily="Arial">Selected Elements Classification:</TextBlock>
            <TextBlock Name="SlctElementsClassification" Margin ="5,20,0,0" Grid.Column="1" FontSize="14" Foreground="CadetBlue" TextWrapping="NoWrap" FontFamily="Arial">nothing Selected</TextBlock>



        </Grid>
        <Grid Grid.Row="1">
            <TextBlock Name="MyTextBlock" 
                       Margin ="5,5,5,5" Grid.Column="1"
                       FontSize="10" Foreground="Black"
                       TextWrapping="Wrap"
                       FontFamily="Arial">
                Mytestinfo1<LineBreak></LineBreak>Mytestinfo2
            </TextBlock>
            <TextBox x:Name="Userinput1" 
                     Text="TextyText"
                     Margin ="5,40,5,5"
                     Height ="20"
                     Width ="Auto"
                     HorizontalAlignment="Left"
                     VerticalAlignment="Top"
                     />
        </Grid>
    </Grid>

</Page>

 

[5] XAML.CS

namespace ILF_Parameters.ui
{
    using System;
    using System.Windows;
    using System.Windows.Controls;

    using Autodesk.Revit.UI;

    using core;

    /// <summary>
    /// Interaktionslogik für ILFParametersMainPage.xaml
    /// </summary>
    public partial class ILFParametersMainPage : Page,  IDockablePaneProvider
    {


        
        #region constructor

        /// <summary>
        /// Default constructor.
        /// Initializes a new instance of the <see cref="ILFParametersMainPage"/> class.
        /// </summary>
        public ILFParametersMainPage()
        {
            InitializeComponent();
        }


        #endregion

        #region public methods

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        /// <exception cref="NotImplementedException"></exception>
        public void Dispose()
        {
            this.Dispose();
        }

        /// <summary>
        /// Setups the dockable pane.
        /// </summary>
        /// <param name="data"></param>
        /// <exception cref="NotImplementedException"></exception>
        public void SetupDockablePane(DockablePaneProviderData data)
        {
            data.FrameworkElement = this as FrameworkElement;


            // Define inital pane position in Revit UI.
            data.InitialState = new DockablePaneState
            {
                DockPosition = DockPosition.Right,
            };

            
        }

        #endregion
    }
}

 

0 Likes
Accepted solutions (1)
857 Views
6 Replies
Replies (6)
Message 2 of 7

RPTHOMAS108
Mentor
Mentor

Unless I am misunderstanding, why do you want to edit the xaml at runtime?

 

To do that you would have to use something like a XamlReader to create the control and add it to the pane. You would then also have to decide how to implement the events of the controls loaded in such a way. Is your pane that open ended that you don't know anything about what it contains? It is probably about twice as complicated using this approach similar to loose xaml.

0 Likes
Message 3 of 7

DomiKlotz
Explorer
Explorer

Hi, thanks for your answer.

 

To further describe what i am trying to achieve:


i want to have a panel which shows me project spezific element paramaters from my current selected elements and their values depending on a classification parameter on element/type level).

the workflow i am trying to achieve is the same as the revit builtin properties panel but to just show me the relevant parameters. The relevant parameters for each classification (and in some cases already predefined values) are stored in an xml file which i was already able to import in my addin.

 

I hope this explains why i want to have access to the xaml which is shown in the dockpane.

0 Likes
Message 4 of 7

RPTHOMAS108
Mentor
Mentor

The nearest equivalent to property grid in WPF would be the DataGrid (you can hide the column and row headers). Then you just have one column for the parameter name (which can be ReadOnly) and one for the value. I don't see the need to change the Xaml at runtime to achieve what you've stated as your objectives. You just need to connect (bind) the DataGrid to an object populated from the element that stores the classification information about the Element selected (I would do this with binding). If the pane has other controls (additional to the property grid/DataGrid) then you can use DataContext to reduce the amount of code you have to write for bindings.

 

If using DataGrid then make sure you set DataGrid.AutoGenerateColumns to false then you can manually add the bound columns (this gives you more control). The DataGrid ItemSource should be pointed to a collection of a class object that contains a property for each column. The binding value of each column can then be set to each of these property names in the mark-up. There are many examples of how to bind a DataGrid online some however are overcomplicated. You rarely have to do more than the below:

 

<DataGridTextColumn Binding="{Binding YourPropertyName}"></DataGridTextColumn>

 

I would often set the ItemSource in code and during a selection changed event I would do the following:

 

DataGrid.ItemsSource = Nothing
DataGrid.ItemsSource = TheCollection

 

This forces the update on the control. I found it is better than the various ways of creating the binding or using INotifyPropertyChanged with an ObservableCollection etc. ObservableCollection doesn't have great functionality compared with List<T> etc. The downside of this approach is that on the control you loose the current selected item but that isn't relevant above (set DataGrid.SelectionUnit ="Cell").

0 Likes
Message 5 of 7

DomiKlotz
Explorer
Explorer

Hi Richard,

 

Again thank you for your reply. Your help is very much appreciated.

Some things you explained i am not quite sure i am understanding right but im slowly making progress.

 

What i managed to implement so far:

Set the DataGrid.ItemSource after Initializing the XAML Page.

Change ItemSource onButtonClick (Button from within the XAML).

 

What I am trying to do now is Click a PushButton to receive the elements Parameters store them in the ItemSorceTarget and Update the ItemSoure (I am trying it from a PushButton Command so i am in a valid Revit API Context where i can get the elements Parameters) but I cant figure out how i can access the DataGrid from the ExternalCommand.

The other Way around (within the ILFParametersMainPage Xaml.cs) I lack access to the Selection...

 

Where do you put the " DataGrid.ItemsSource = TheCollection " ?

How do I get the connection from the relevant RevitData and the DataGrid.ItemsSource?

Is there an easy Example how to write the ElementIDs of the Current Selection to this DataGrid so i can understand the Logic?

 

 

XAML.CS

namespace ILF_Parameters.core
{
    using System;
    using System.Windows;
    using System.Windows.Controls;
    using System.Xaml;
    using Autodesk.Revit.UI;

    /// <summary>
    /// Interactionlogic for ILFParametersMainPage.xaml
    /// </summary>
    public partial class ILFParametersMainPage : Page, IDockablePaneProvider
    {
        public string fromMain;

        #region constructor

        /// <summary>
        /// Default constructor.
        /// Initializes a new instance of the <see cref="ILFParametersMainPage"/> class.
        /// </summary>
        public ILFParametersMainPage()
        {
            InitializeComponent();
            dataGrid1.ItemsSource = ElementData.GetDummyElementData();
        }

        private void ClickB1(object sender, RoutedEventArgs e)
        {         
            dataGrid1.ItemsSource = ElementData.GetNewDummyElementData();
        }

        private void ClickB2(object sender, RoutedEventArgs e)
        {
            dataGrid1.ItemsSource = null;
        }

        private void ClickB3(object sender, RoutedEventArgs e)        
        {
            dataGrid1.ItemsSource = ElementData.GetDummyElementData();
        }
        #endregion

        #region public methods
        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        /// <exception cref="NotImplementedException"></exception>
        public void Dispose()
        {
            this.Dispose();
        }
        /// <summary>
        /// Setups the dockable pane.
        /// </summary>
        /// <param name="data"></param>
        /// <exception cref="NotImplementedException"></exception>
        public void SetupDockablePane(DockablePaneProviderData data)
        {
            data.FrameworkElement = this as FrameworkElement;
            // Define inital pane position in Revit UI.
            data.InitialState = new DockablePaneState
            {
                DockPosition = DockPosition.Right,
            };
        }
        #endregion
    }
}

 

XAML

<Page x:Class="ILF_Parameters.core.ILFParametersMainPage"
        x:Name="ThisisMyMainPage"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:ILF_Parameters.core"
        mc:Ignorable="d"
        d:DesignHeight="300" d:DesignWidth="300"
        Title="ILF Parameters">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="20"></RowDefinition>
            <RowDefinition Height="500"></RowDefinition>
            <RowDefinition Height="30"></RowDefinition>
            <RowDefinition Height="30"></RowDefinition>
            <RowDefinition Height="30"></RowDefinition>
        </Grid.RowDefinitions>
        <Border Grid.Row="0" Background="LightGray">
            <TextBlock>TEST</TextBlock>
        </Border>
        <DataGrid Grid.Row ="1" Background="White" x:Name="dataGrid1" AutoGenerateColumns="False" >
            <DataGrid.Columns>
                <DataGridTextColumn Binding="{Binding ParameterName}" IsReadOnly="True"></DataGridTextColumn>
                <DataGridTextColumn Binding="{Binding ParameterValue}"></DataGridTextColumn>
            </DataGrid.Columns>

        </DataGrid>
        <Button Name="MyTestButton" Content="GetNewDummyData" Grid.Row="2" Click="ClickB1"></Button>
        <Button Name="MyTestButton2" Content="Clear" Grid.Row="3" Click="ClickB2"></Button>
        <Button Name="MyTestButton3" Content="GetOldDummyData" Grid.Row="4" Click="ClickB3"></Button>
    </Grid>
</Page>

 

UpdateDockPane : IExternalCommand

namespace ILF_Parameters.core
{
    using Autodesk.Revit.DB;
    using Autodesk.Revit.UI;
    using Autodesk.Revit.UI.Selection;
    using System;
    using System.Collections.Generic;


    /// <summary>
    /// Command code to be executed when button is clicked.
    /// </summary>
    /// <seealso cref="Autodesk.Revit.UI.IexternalCommand"/>
    [Autodesk.Revit.Attributes.Transaction(Autodesk.Revit.Attributes.TransactionMode.Manual)][Autodesk.Revit.Attributes.Regeneration(Autodesk.Revit.Attributes.RegenerationOption.Manual)]
    public class UpdateDockpane : IExternalCommand
    {
        #region public methods
        public Autodesk.Revit.UI.Result Execute(ExternalCommandData commandData,
        ref string message, ElementSet elements)
        {
            TaskDialog.Show("Revit", "TestyDoesMybuttonWork?");
            //GetElementParameter -> Push to new ElementDataModell
            //Update Data Grid item Source here?
            return Autodesk.Revit.UI.Result.Succeeded;
        }
        /// <summary>
        /// Gets the full namespace path to this command.
        /// </summary>
        /// <returns></returns>
        public static string GetPath()
        {
            // Return constructed namespace path.
            return typeof(UpdateDockpane).Namespace + "." + nameof(UpdateDockpane);
        }
        #endregion
    }
}

 

0 Likes
Message 6 of 7

RPTHOMAS108
Mentor
Mentor
Accepted solution

On the class that creates your pane i.e. the one that implements IExternalApplication you can have a static variable for both the pane and the collection the DataGrid is bound to.

 

So both can then be updated from elsewhere e.g. you may have an instance method on the pane responsible for updating the binding (as I noted previously) which is accessed via the pane object held in the static variable. I don't think you will encounter issues doing that. Sometimes more generally the Window can only be updated by the UI thread i.e. the thread that created the control. If you encounter this than then you can add a static method to the pane whereby you get the dispatcher for the instance of the pane and update it that way. First things first though.

 

To interact with Revit from dockable pane you will have to use IExternalEventHandler. I would refer to the modeless dialogues samples in the SDK as there are many non-trivial puzzle pieces to this task and it is better not to reinvent them.

0 Likes
Message 7 of 7

DomiKlotz
Explorer
Explorer

 

 

Hello RPTHOMAS,

 

Thanks for your help. i was able to change the content now.

 

 

Class that Registers the Dockpane:

public class Register_Parameters_Pane_Command : IExternalCommand
    {
        //static variable for Accessing the MainPageInstance during RunTime from other External Commands
        public static ParametersMainPage ParametersPageInstance;

        public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)
        {
            return Execute(commandData.Application);
        }

        public Result Execute(UIApplication uIApplication)
        {
            DockablePaneProviderData data = new DockablePaneProviderData();
            ParametersMainPage ParametersPage = new ParametersMainPage();

			//store new created ParametersMainPage in static variable ParametersPageInstance.
            ParametersPageInstance = ParametersPage;

            data.FrameworkElement = ParametersPage as FrameworkElement;

            var state = new DockablePaneState
            {
                DockPosition = DockPosition.Right,
            };

            

            // Use unique guid identifier for this dockable pane.
            var dpid = new DockablePaneId(PaneIdentifiers.GetParaPaneId());

            uIApplication.RegisterDockablePane(dpid, "Parameters", (IDockablePaneProvider)ParametersPage);

            
            return Result.Succeeded;
        }

    }

 

on the command that changes the content i can get reference to the instance of myPage in Dockpane (and call its methods like ClearPane in this example code snippet):

//get the ParametersMainPageInstance and clear content
ParametersMainPage ParametersPage = Register_Parameters_Pane_Command.ParametersPageInstance;


ParametersPage.ClearPane();

 

Best Regards,

Domi

0 Likes