Forms

The forms module provide several pre-build forms as well as a framework from which you can build your own forms.

All classes documented in this section can be imported as such:

>>> from rpw.ui.forms import Console

QuickForms

SelectFromList

../_images/select-from-list.png
rpw.ui.forms.SelectFromList(title, options, description=None, sort=True, exit_on_close=True)

Simple FlexForm wrapped function with ComboBox and button

Parameters:
  • title (str) – Title of form
  • options (dict,list[str]) – Dictionary (string keys) or List[strings]
  • description (str) – Optional Description of input requested [default: None]
  • sort (bool) – Optional sort flag - sorts keys [default: True]
  • exit_on_close (bool) – Form will call sys.exit() if Closed on X. [default: True]

Usage:

>>> from rpw.ui.forms import SelectFromList
>>> value = SelectFromList('Test Window', ['1','2','3'])
>>> # Dropdown shows '1', '2' ,'3'. User clicks Select '1'
>>> print(value)
'1'
>>> # Dictionary
>>> value = SelectFromList('Test Window', {'Text':str, 'Number':int})
>>> # User clicks Text
>>> print(value)
str

TextInput

../_images/text-input.png
rpw.ui.forms.TextInput(title, default=None, description=None, sort=True, exit_on_close=True)

Simple FlexForm wrapped function with TextBox and button

Parameters:
  • title (str) – Title of form
  • default (str) – Optional default value for text box [default: None]
  • description (str) – Optional Description of input requested [default: None]
  • exit_on_close (bool) – Form will call sys.exit() if Closed on X. [default: True]

Usage:

>>> from rpw.ui.forms import TextInput
>>> value = TextInput('Title', default="3")
>>> print(value)
3

TaskDialogs

TaskDialog

../_images/taskdialog.png
class rpw.ui.forms.TaskDialog(instruction, commands=None, buttons=None, title='Task Dialog', content='', title_prefix=False, show_close=False, footer='', expanded_content='', verification_text='')

Task Dialog Wrapper

>>> from rpw.ui.forms import CommandLink, TaskDialog
>>> commands= [CommandLink('Open Dialog', return_value='Open'),
>>> ...           CommandLink('Command', return_value=lambda: True)]
>>> ...
>>> dialog = TaskDialog('This TaskDialog has Buttons ',
>>> ...                 title_prefix=False,
>>> ...                 content="Further Instructions",
>>> ...                 commands=commands,
>>> ...                 buttons=['Cancel', 'OK', 'RETRY'],
>>> ...                 footer='It has a footer',
>>> ...                 # verification_text='Add Verification Checkbox',
>>> ...                 # expanded_content='Add Expanded Content',
>>> ...                 show_close=True)
>>> dialog.show()
'Open'
Wrapped Element:
self._revit_object = Revit.UI.TaskDialog
Parameters:
  • content (str) – Main text of TaskDialog.
  • commands (list, optional) – List of CommandLink Instances. Default is no buttons.
  • buttons (list, optional) – List of TaskDialogCommonButtons names. Default is no buttons. ‘Close’ is shown if no commands are passed.
  • title (str, optional) – Title of TaskDialog. Default is ‘Task Dialog’.p
  • instruction (str, optional) – Main Instruction.
  • footer (str, optional) – Footer Text. Default is blank.
  • expanded_content (str, optional) – Expandable Text. Default is blank.
  • verification_text (str, optional) – Checkbox text. Default is blank.
  • title_prefix (bool, optional) – Prefix Title with app name. Default is False
  • show_close (bool, optional) – Show X to close. Default is False.
show(exit=False)

Show TaskDialog

Parameters:exit (bool, optional) – Exit Script after Dialog. Useful for displaying Errors. Default is False.
Returns:Returns is False if dialog is Cancelled (X or Cancel button). If CommandLink button is clicked, CommandLink.return_value is returned - if one was not provided, CommandLink.text is used. If CommonButtons are clicked TaskDialog.TaskDialogResult name is returned ie(‘Close’, ‘Retry’, ‘Yes’, etc).

Command Link Helper Class

Usage:
>>> from rpw.ui.forms import CommandLink, TaskDialog
>>> CommandLink('Open Dialog', return_value=func_to_open)
>>> TaskDialog('Title', commands=[CommandLink])
Parameters:
  • text (str) – Command Text
  • subtext (str, optional) – Button Subtext
  • return_value (any, optional) – Value returned if button is clicked. If none is provided, text is returned.

Alert

../_images/alert.png
class rpw.ui.forms.Alert(content, title='Alert', header='', exit=False)

A Simple Revit TaskDialog for displaying quick messages

Usage:
>>> from rpw.ui.forms import Alert
>>> Alert('Your Message', title="Title", header="Header Text")
>>> Alert('You need to select Something', exit=True)
Parameters:
  • message (str) – TaskDialog Content
  • title (str, optional) – TaskDialog Title
  • header (str, optional) – TaskDialog content header
  • exit (bool, optional) – Exit Script after Dialog. Useful for displayin Errors. Default is False

OS Dialogs

Select Folder

../_images/select_folder.png
rpw.ui.forms.select_folder()

Selects a Folder Path using the standard OS Dialog. Uses Forms.FolderBrowserDialog(). For more information see: https://msdn.microsoft.com/en-us/library/system.windows.forms.openfiledialog.

>>> from rpw.ui.forms import select_folder
>>> folderpath = select_folder()
'C:\folder\path'

Select File

../_images/select_file.png
rpw.ui.forms.select_file(extensions='All Files (*.*)|*.*', title='Select File', multiple=False, restore_directory=True)

Selects a File Path using the standard OS Dialog. Uses Forms.OpenFileDialog https://msdn.microsoft.com/en-us/library/system.windows.forms.filedialog.filter

>>> from rpw.ui.forms import select_file
>>> filepath = select_file('Revit File ('*.rvt)|*.rvt')
'C:\folder\file.rvt'
Parameters:
  • extensions (str, optional) – File Extensions Filtering options. Default is All Files (.)|*.*
  • title (str, optional) – File Extensions Filtering options
  • multiple (bool) – Allow selection of multiple files. Default is False
  • restore_directory (bool) – Restores the directory to the previously selected directory before closing

Console

../_images/console.png

REPL Console for Inspecting Stack

>>> from rpw.ui.forms import Console
>>> Console()
# Console is launched with locally defined variables injected into context.
Keyboard Shortcuts:
  • UP Iterate history up
  • Down Iterate history down
  • Tab Iterate possible autocomplete options (works for dotted lookup)

Note

The last stack frame is automatically injected is the context of the evaluation loop of the console: the local and global variables from where the Console was called from should be available.

Inspection of the stack requires stack frames to be enabled. If an exception is raised stating `object has no attribute '_getframe' it means IronPython stack frames is not enabled. You can enable it by running with the -X argument: ipy.exe -X: FullFrames file.py.

If you are trying to use it from within Dynamo, stack inspection is currently not available due to how the engine is setup, but you can still use it by manually passing the context you want to inspect:

>>> Console(context=locals())  # or
>>> Console(context=globals())
class rpw.ui.forms.Console(stack_level=1, stack_info=True, context=None, msg='')

FlexForm

class rpw.ui.forms.FlexForm(title, components, **kwargs)

Flex Form Usage

>>> from rpw.ui.forms import (FlexForm, Label, ComboBox, TextBox, TextBox,
...                           Separator, Button)
>>> components = [Label('Pick Style:'),
...               ComboBox('combobox1', {'Opt 1': 10.0, 'Opt 2', 20.0}),
...               Label('Enter Name:'),
...               TextBox('textbox1', Text="Default Value"),
...               CheckBox('checkbox1', 'Check this'),
...               Separator(),
...               Button('Select')]
>>> form = FlexForm('Title', components)
>>> form.show()
>>> # User selects `Opt 1`, types 'Wood' in TextBox, and select Checkbox
>>> form.values
{'combobox1': 10.0, 'textbox1': 'Wood', 'checkbox': True}
__init__(title, components, **kwargs)
Parameters:
  • title (str) – Form Title
  • components (list) – List of Form Components.
  • top_offset (float) – Optional top offset.
  • options (kwargs) – WPF Parameters Options
values

dict – Dictionary of selected values

close()

Exits Form. Returns True to show() method

static get_values(sender, e)

Default Get Values. Set form.values attribute with values from controls and closes form.

show()

Initializes Form. Returns True or False if form was exited.

../_images/flexform.png

FlexForm Controls

class rpw.ui.forms.Label(label_text, **kwargs)

Windows.Controls.Label Wrapper

>>> Label('Label Text')
__init__(label_text, **kwargs)
Parameters:
  • label_text (str) – Label Text
  • wpf_params (kwargs) – Additional WPF attributes
class rpw.ui.forms.TextBox(name, default='', **kwargs)

Windows.Controls.TextBox Wrapper

>>> TextBox()
__init__(name, default='', **kwargs)
Parameters:
  • name (str) – Name of control. Will be used to return value
  • default (bool) – Sets Text attribute of textbox [Default: ‘’]
  • wpf_params (kwargs) – Additional WPF attributes
class rpw.ui.forms.CheckBox(name, checkbox_text, default=False, **kwargs)

Windows.Controls.Checkbox Wrapper

>>> CheckBox('Label')
__init__(name, checkbox_text, default=False, **kwargs)
Parameters:
  • name (str) – Name of control. Will be used to return value
  • checkbox_text (str) – Checkbox label Text
  • default (bool) – Sets IsChecked state [Default: False]
  • wpf_params (kwargs) – Additional WPF attributes
class rpw.ui.forms.ComboBox(name, options, default=None, sort=True, **kwargs)

Windows.Controls.ComboBox Wrapper

>>> ComboBox({'Option 1': Element, 'Option 2', 'Elemnet'})
>>> ComboBox({'Option 1': Element, 'Option 2', 'Elemnet'}, sort=False)
__init__(name, options, default=None, sort=True, **kwargs)
Parameters:
  • name (str) – Name of control. Will be used to return value
  • options (list, dict) – If dict, selected value is returned
  • default (str) – Sets SelectedItem attribute [Default: first]
  • wpf_params (kwargs) – Additional WPF attributes
class rpw.ui.forms.Button(button_text, on_click=None, **kwargs)

Windows.Controls.Button Wrapper

>>> Button('Select')
__init__(button_text, on_click=None, **kwargs)
Parameters:
  • button_text (str) – Button Text
  • on_click (func) – Registers Click event Function [Default: FlexForm.get_values]
  • wpf_params (kwargs) – Additional WPF attributes
class rpw.ui.forms.Separator(**kwargs)

WPF Separator


Implementations

FlexForm


from itertools import count

from rpw.utils.dotnet import Enum
from rpw.ui.forms.resources import *


class FlexForm(Window):
    """
    Flex Form Usage

    >>> from rpw.ui.forms import (FlexForm, Label, ComboBox, TextBox, TextBox,
    ...                           Separator, Button)
    >>> components = [Label('Pick Style:'),
    ...               ComboBox('combobox1', {'Opt 1': 10.0, 'Opt 2', 20.0}),
    ...               Label('Enter Name:'),
    ...               TextBox('textbox1', Text="Default Value"),
    ...               CheckBox('checkbox1', 'Check this'),
    ...               Separator(),
    ...               Button('Select')]
    >>> form = FlexForm('Title', components)
    >>> form.show()
    >>> # User selects `Opt 1`, types 'Wood' in TextBox, and select Checkbox
    >>> form.values
    {'combobox1': 10.0, 'textbox1': 'Wood', 'checkbox': True}

    """
    layout = """
            <Window
                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:WpfApplication1" mc:Ignorable="d"
                ResizeMode="NoResize"
                WindowStartupLocation="CenterScreen"
                Topmost="True"
                SizeToContent="WidthAndHeight">

                <Grid Name="MainGrid" Margin="10,10,10,10">
                </Grid>
            </Window>
            """

    def __init__(self, title, components, **kwargs):
        """
        Args:
            title (``str``): Form Title
            components (``list``): List of Form Components.
            top_offset (``float``): Optional top offset.
            options (``kwargs``): WPF Parameters Options

        Attributes:
            values (``dict``): Dictionary of selected values
        """

        self.ui = wpf.LoadComponent(self, StringReader(self.layout))
        self.ui.Title = title
        self.values = {}

        for key, value in kwargs.iteritems():
            setattr(self, key, value)

        for n, component in enumerate(components):
            self.MainGrid.Children.Add(component)
            if hasattr(component, 'on_click'):
                component.Click += component.on_click

            V_SPACE = 5
            if n > 0:
                prev_comp = components[n - 1]
                top = prev_comp.Margin.Top + prev_comp.Height + V_SPACE
                top += getattr(component, 'top_offset', 0)
                component.Margin = Thickness(0, top, 0, 0)

    def show(self):
        """
        Initializes Form. Returns ``True`` or ``False`` if form was exited.
        """
        return self.ShowDialog()

    @staticmethod
    def get_values(sender, e):
        """
        Default Get Values. Set form.values attribute with values from controls
        and closes form.
        """
        component_values = {}
        window = Window.GetWindow(sender)
        for component in window.MainGrid.Children:
            try:
                component_values[component.Name] = component.value
            except AttributeError:
                pass
        window.values = component_values
        window.close()

    def close(self):
        """ Exits Form. Returns ``True`` to ``show()`` method """
        self.DialogResult = True
        self.Close()


class RpwControlMixin():
    """ Control Mixin """
    _index = count(0)

    def __init__(self, **kwargs):
        self.set_attrs(**kwargs)

    def set_attrs(self, **kwargs):
        """ Parses kwargs, sets default values where appropriate. """
        self.index = next(self._index)  # Counts Instatiation to control Height

        # Default Values
        control_type = self.__class__.__name__
        if not self.Name:
            self.Name = kwargs.get('Name', '{}_{}'.format(control_type, self.index))

        self.Width = kwargs.get('Width', 300)
        self.Height = kwargs.get('Height', 25)

        h_align = Enum.Parse(HorizontalAlignment, kwargs.get('h_align', 'Left'))
        self.HorizontalAlignment = h_align
        v_align = Enum.Parse(VerticalAlignment, kwargs.get('v_align', 'Top'))
        self.VerticalAlignment = v_align

        # Inject Any other Custom Values into Component
        # Updating __dict__ fails due to how .NET inheritance/properties works
        for key, value in kwargs.iteritems():
            setattr(self, key, value)


class Label(RpwControlMixin, Controls.Label):
    """
    Windows.Controls.Label Wrapper

    >>> Label('Label Text')
    """
    def __init__(self, label_text, **kwargs):
        """
        Args:
            label_text (``str``): Label Text
            wpf_params (kwargs): Additional WPF attributes
        """
        self.Content = label_text
        self.set_attrs(**kwargs)


class TextBox(RpwControlMixin, Controls.TextBox):
    """
    Windows.Controls.TextBox Wrapper

    >>> TextBox()
    """
    def __init__(self, name, default='', **kwargs):
        """
        Args:
            name (``str``): Name of control. Will be used to return value
            default (``bool``): Sets ``Text`` attribute of textbox [Default: '']
            wpf_params (kwargs): Additional WPF attributes
        """
        self.Name = name
        self.Text = default
        self.set_attrs(**kwargs)
        if 'Height' not in kwargs:
            self.Height = 25

    @property
    def value(self):
        return self.Text


class Button(RpwControlMixin, Controls.Button):
    """
    Windows.Controls.Button Wrapper

    >>> Button('Select')
    """
    def __init__(self, button_text, on_click=None, **kwargs):
        """
        Args:
            button_text (``str``): Button Text
            on_click (``func``): Registers Click event Function [Default: :any:`FlexForm.get_values`]
            wpf_params (kwargs): Additional WPF attributes
        """
        self.Content = button_text
        self.on_click = on_click or FlexForm.get_values
        self.set_attrs(**kwargs)


class CheckBox(RpwControlMixin, Controls.CheckBox):
    """
    Windows.Controls.Checkbox Wrapper

    >>> CheckBox('Label')
    """
    def __init__(self, name, checkbox_text, default=False, **kwargs):
        """
        Args:
            name (``str``): Name of control. Will be used to return value
            checkbox_text (``str``): Checkbox label Text
            default (``bool``): Sets IsChecked state [Default: False]
            wpf_params (kwargs): Additional WPF attributes
        """
        self.Name = name
        self.Content = checkbox_text
        self.IsChecked = default
        self.set_attrs(top_offset=5, **kwargs)

    @property
    def value(self):
        return self.IsChecked


class ComboBox(RpwControlMixin, Controls.ComboBox):
    """
    Windows.Controls.ComboBox Wrapper

    >>> ComboBox({'Option 1': Element, 'Option 2', 'Elemnet'})
    >>> ComboBox({'Option 1': Element, 'Option 2', 'Elemnet'}, sort=False)
    """
    def __init__(self, name, options, default=None, sort=True, **kwargs):
        """
        Args:
            name (``str``): Name of control. Will be used to return value
            options (``list``, ``dict``): If ``dict``, selected value is returned
            default (``str``): Sets SelectedItem attribute [Default: first]
            wpf_params (kwargs): Additional WPF attributes
        """
        self.Name = name
        self.set_attrs(**kwargs)
        index = 0

        self.options = options
        if hasattr(options, 'keys'):
            options = options.keys()
        if sort:
            options.sort()
        if default is None:
            index = 0
        else:
            index = options.index(default)

        self.Items.Clear()
        self.ItemsSource = options
        self.SelectedItem = options[index]

    @property
    def value(self):
        selected = self.SelectedItem
        if isinstance(self.options, dict):
            selected = self.options[selected]
        return selected

class Separator(RpwControlMixin, Controls.Separator):
    """ WPF Separator """


if __name__ == '__main__':
    """ TESTS """
    components = [
                  Label('Pick Style:'),
                  ComboBox('combobox1', {'Opt 1': 10.0, 'Opt 2': 20.0}),
                  Label('Enter Name:'),
                  TextBox('textbox1', Text="Default Value"),
                  CheckBox('checkbox1', 'Check this:'),
                  Separator(),
                  Button('Select')]

    form = FlexForm('Title', components)
    form.show()

    print(form.values)

QuickForm


import sys

from rpw.ui.forms.flexform import FlexForm, Label, ComboBox, TextBox, Button

def SelectFromList(title, options, description=None, sort=True, exit_on_close=True):
    """ Simple FlexForm wrapped function with ComboBox and button

    Args:
        title (str): Title of form
        options (dict,list[str]): Dictionary (string keys) or List[strings]
        description (str): Optional Description of input requested  [default: None]
        sort (bool): Optional sort flag - sorts keys [default: True]
        exit_on_close (bool): Form will call sys.exit() if Closed on X. [default: True]

    Usage:

        >>> from rpw.ui.forms import SelectFromList
        >>> value = SelectFromList('Test Window', ['1','2','3'])
        >>> # Dropdown shows '1', '2' ,'3'. User clicks Select '1'
        >>> print(value)
        '1'
        >>> # Dictionary
        >>> value = SelectFromList('Test Window', {'Text':str, 'Number':int})
        >>> # User clicks Text
        >>> print(value)
        str
    """
    components = []
    if description:
        components.append(Label(description))
    components.append(ComboBox('combobox', options, sort=sort))
    components.append(Button('Select'))
    form = FlexForm(title, components)
    ok = form.show()
    if ok:
        return form.values['combobox']
    if exit_on_close:
        sys.exit()


def TextInput(title, default=None, description=None, sort=True, exit_on_close=True):
    """ Simple FlexForm wrapped function with TextBox and button

    Args:
        title (str): Title of form
        default (str): Optional default value for text box [default: None]
        description (str): Optional Description of input requested  [default: None]
        exit_on_close (bool): Form will call sys.exit() if Closed on X. [default: True]

    Usage:

        >>> from rpw.ui.forms import TextInput
        >>> value = TextInput('Title', default="3")
        >>> print(value)
        3
    """
    components = []
    if description:
        components.append(Label(description))
    if default:
        textbox = TextBox('textbox', default=default)
    else:
        textbox = TextBox('textbox')
    components.append(textbox)
    components.append(Button('Select'))
    form = FlexForm(title, components)
    ok = form.show()
    if ok:
        return form.values['textbox']
    if exit_on_close:
        sys.exit()


if __name__ == '__main__':
    rv = SelectFromList('Title', ['A','B'], description="Your Options",
                            exit_on_close=True)
    print(rv)

    rv = SelectFromList('Title', {'A':5, 'B':10}, description="Your Options",
                            exit_on_close=True)
    print(rv)

    rv = TextInput('Title', default="3", exit_on_close=True)
    print(rv)
    print('forms.py ran')

TaskDialog


import sys
from rpw import UI
from rpw.exceptions import RpwValueError
from rpw.base import BaseObjectWrapper, BaseObject

class Alert():
    """
    A Simple Revit TaskDialog for displaying quick messages

    Usage:
        >>> from rpw.ui.forms import Alert
        >>> Alert('Your Message', title="Title", header="Header Text")
        >>> Alert('You need to select Something', exit=True)

    Args:
        message (str): TaskDialog Content
        title (str, optional): TaskDialog Title
        header (str, optional): TaskDialog content header
        exit (bool, optional): Exit Script after Dialog.
            Useful for displayin Errors. Default is False

    """
    def __init__(self, content, title='Alert', header='', exit=False):
        dialog = UI.TaskDialog(title)
        dialog.TitleAutoPrefix = False
        dialog.MainInstruction = header
        dialog.MainContent = content
        self.result = dialog.Show()

        if exit:
            sys.exit(1)

class CommandLink(BaseObject):
    """
    Command Link Helper Class

    Usage:
        >>> from rpw.ui.forms import CommandLink, TaskDialog
        >>> CommandLink('Open Dialog', return_value=func_to_open)
        >>> TaskDialog('Title', commands=[CommandLink])

    Args:
        text (str): Command Text
        subtext (str, optional): Button Subtext
        return_value (any, optional): Value returned if button is clicked.
            If none is provided, text is returned.

    """
    def __init__(self, text, subtext='', return_value=None):
        self._id = None  # This will later be set to TaskDialogCommandLinkId(n)
        self.text = text
        self.subtext = subtext
        self.return_value = return_value if return_value is not None else text

    def __repr__(self):
        return super(CommandLink, self).__repr__(data={'id': self._id,
                                                       'text':self.text})


class TaskDialog(BaseObjectWrapper):
    """
    Task Dialog Wrapper

    >>> from rpw.ui.forms import CommandLink, TaskDialog
    >>> commands= [CommandLink('Open Dialog', return_value='Open'),
    >>> ...           CommandLink('Command', return_value=lambda: True)]
    >>> ...
    >>> dialog = TaskDialog('This TaskDialog has Buttons ',
    >>> ...                 title_prefix=False,
    >>> ...                 content="Further Instructions",
    >>> ...                 commands=commands,
    >>> ...                 buttons=['Cancel', 'OK', 'RETRY'],
    >>> ...                 footer='It has a footer',
    >>> ...                 # verification_text='Add Verification Checkbox',
    >>> ...                 # expanded_content='Add Expanded Content',
    >>> ...                 show_close=True)
    >>> dialog.show()
    'Open'

    Wrapped Element:
        self._revit_object = `Revit.UI.TaskDialog`

    Args:
        content (str): Main text of TaskDialog.
        commands (list, optional): List of CommandLink Instances.
            Default is no buttons.
        buttons (list, optional): List of TaskDialogCommonButtons names.
            Default is no buttons. 'Close' is shown if no commands are passed.
        title (str, optional): Title of TaskDialog. Default is 'Task Dialog'.p
        instruction (str, optional): Main Instruction.
        footer (str, optional): Footer Text. Default is ``blank``.
        expanded_content (str, optional): Expandable Text. Default is ``blank``.
        verification_text (str, optional): Checkbox text. Default is ``blank``.
        title_prefix (bool, optional): Prefix Title with app name.
            Default is ``False``
        show_close (bool, optional): Show X to close. Default is False.

    """
    _revit_object_class = UI.TaskDialog
    _common_buttons = ['Ok', 'Yes', 'No', 'Cancel', 'Retry', 'Close']

    def __init__(self, instruction, commands=None, buttons=None,
                 title='Task Dialog', content='',
                 title_prefix=False, show_close=False,
                 footer='', expanded_content='', verification_text=''
                 ):

        super(TaskDialog, self).__init__(UI.TaskDialog(title))
        self.dialog = self._revit_object

        # Settings
        self.dialog.TitleAutoPrefix = title_prefix
        self.dialog.AllowCancellation = show_close

        # Properties
        self.dialog.Title = title
        self.dialog.MainInstruction = instruction
        self.dialog.MainContent = content
        self.dialog.FooterText = footer
        self.dialog.ExpandedContent = expanded_content
        self.dialog.VerificationText = verification_text
        self.verification_checked = None if not verification_text else False

        # Add Buttons
        self.buttons = buttons or []
        common_buttons_names = []
        for button_name in [b.capitalize() for b in self.buttons]:
            if button_name not in self._common_buttons:
                raise RpwValueError('TaskDialogCommonButtons member', button_name)
            button_full_name = 'UI.TaskDialogCommonButtons.' + button_name
            common_buttons_names.append(button_full_name)

        if common_buttons_names:
            common_buttons = eval('|'.join(common_buttons_names))
            self.dialog.CommonButtons = common_buttons

        # Set Default Button
        self.dialog.DefaultButton = UI.TaskDialogResult.None

        # Validate Commands
        commands = commands or []
        if len(commands) > 4:
            raise RpwValueError('4 or less command links', len(commands))

        # Process Commands
        self.commands = {}
        for link_index, command_link in enumerate(commands, 1):
            command_id = 'CommandLink{}'.format(link_index)
            command_link._id = getattr(UI.TaskDialogCommandLinkId, command_id)
            self.commands[command_id] = command_link
            self.dialog.AddCommandLink(command_link._id,
                                       command_link.text,
                                       command_link.subtext)

    def show(self, exit=False):
        """
        Show TaskDialog

        Args:
            exit (bool, optional): Exit Script after Dialog. Useful for
                displaying Errors. Default is False.

        Returns:
            Returns is ``False`` if dialog is Cancelled (X or Cancel button).
            If CommandLink button is clicked, ``CommandLink.return_value``
            is returned - if one was not provided, ``CommandLink.text`` is used.
            If CommonButtons are clicked ``TaskDialog.TaskDialogResult`` name is
            returned ie('Close', 'Retry', 'Yes', etc).
        """
        self.result = self.dialog.Show()

        try:
            self.verification_checked = self.dialog.WasVerificationChecked()
        except:
            self.verification_checked = None

        # Handle Cancel
        if self.result == UI.TaskDialogResult.Cancel:
            if exit:
                sys.exit(1)
            return None

        # If result is a CommandLink, return Return Value else Result
        command_link = self.commands.get(str(self.result))
        if command_link:
            return command_link.return_value
        else:
            return self.result.ToString()



if __name__ == '__main__':
    Alert('Test Alert!')

    def sample_callback():
        print('Calling B')
        d = UI.TaskDialog("Revit Build Information")
        d.MainInstruction = "Button 1"
        d.Show()


    from rpw.ui.forms.taskdialog import *
    commands = [
                CommandLink('TestTitle', return_value=sample_callback, subtext='test subtext'),
                CommandLink('TestTitle2', return_value=lambda: 'Empty', subtext='test subtext2')
                ]

    t = TaskDialog(commands=commands, buttons=['Yes'])

OS Dialogs


from rpw.ui.forms.resources import *

def select_folder():
    """
    Selects a Folder Path using the standard OS Dialog.
    Uses Forms.FolderBrowserDialog(). For more information see:
    https://msdn.microsoft.com/en-us/library/system.windows.forms.openfiledialog.

    >>> from rpw.ui.forms import select_folder
    >>> folderpath = select_folder()
    'C:\\folder\\path'
    """

    form = Forms.FolderBrowserDialog()
    if form.ShowDialog() == Forms.DialogResult.OK:
        return form.SelectedPath


def select_file(extensions='All Files (*.*)|*.*',
                title="Select File",
                multiple=False,
                restore_directory=True):
    """
    Selects a File Path using the standard OS Dialog.
    Uses Forms.OpenFileDialog
    https://msdn.microsoft.com/en-us/library/system.windows.forms.filedialog.filter

    >>> from rpw.ui.forms import select_file
    >>> filepath = select_file('Revit File ('*.rvt)|*.rvt')
    'C:\\folder\\file.rvt'

    Args:
        extensions (str, optional): File Extensions Filtering options. Default is All Files (*.*)|*.*
        title (str, optional): File Extensions Filtering options
        multiple (bool): Allow selection of multiple files. Default is `False`
        restore_directory (bool): Restores the directory to the previously selected directory before closing

    """
    form = Forms.OpenFileDialog()
    form.Filter = extensions
    form.Title = title
    form.Multiselect = multiple
    form.RestoreDirectory = restore_directory
    if form.ShowDialog() == Forms.DialogResult.OK:
        return form.FileName

# Tests
if __name__ == '__main__':
    print(select_folder())
    print(select_file('Python Files|*.py'))

Console


import os
import inspect
import logging
import tempfile
from collections import defaultdict
import traceback

from rpw.utils.rlcompleter import Completer
from rpw.ui.forms.resources import Window
from rpw.ui.forms.resources import *
# logger.verbose(False)


class Console(Window):
    LAYOUT = """
                <Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                        Title="DeployWindow" Height="400" Width="800" SnapsToDevicePixels="True"
                        UseLayoutRounding="True" WindowState="Normal"
                        WindowStartupLocation="CenterScreen">
                <Window.Resources>
                    <Style TargetType="{x:Type MenuItem}">
                        <Setter Property="FontFamily" Value="Consolas"/>
                        <Setter Property="FontSize" Value="12.0"/>
                    </Style>
                </Window.Resources>
                <Grid>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="*"></ColumnDefinition>
                    </Grid.ColumnDefinitions>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="0"></RowDefinition>
                        <RowDefinition Height="*"></RowDefinition>
                    </Grid.RowDefinitions>
                    <TextBox Grid.Column="1" Grid.Row="1"  HorizontalAlignment="Stretch"
                             Name="tbox" Margin="6,6,6,6" VerticalAlignment="Stretch"
                             AcceptsReturn="True" VerticalScrollBarVisibility="Auto"
                             TextWrapping="Wrap"
                             FontFamily="Consolas" FontSize="12.0"
                             />
                </Grid>
                </Window>
    """
    # <Button Content="Terminate" Margin="6,6,6,6" Height="30"
    #     Grid.Column="1" Grid.Row="1" VerticalAlignment="Bottom"
    #     Click="terminate"></Button>

    CARET = '>>> '

    def __init__(self, stack_level=1, stack_info=True, context=None, msg=''):
        """
        Args:
            stack_level (int): Default is 1. 0 Is the Console stack, 1 is the
                               caller; 2 is previous to that, etc.
            stack_info (bool): Display info about where call came from.
                               Will print filename name,  line no. and Caller
                               name.
           msg (str): Message to display on start.
                      Only available if using context
            context (dict): Optional Dictionary for when inspection is not
                            possible.
        """

        # History Helper
        tempdir = tempfile.gettempdir()
        filename = 'rpw-history'
        self.history_file = os.path.join(tempdir, filename)

        self.stack_locals = {}
        self.stack_globals = {}
        self.stack_level = stack_level

        if context:
            self.stack_locals.update(context)
            # Allows to pass context manually, so it can be used in Dynamo
            # Where inspection does not work
        else:
            # Stack Info
            # stack_frame = inspect.currentframe().f_back
            stack_frame = inspect.stack()[stack_level][0]  # Finds Calling Stack

            self.stack_locals.update(stack_frame.f_locals)
            self.stack_globals.update(stack_frame.f_globals)
            # Debug Console
            self.stack_globals.update({'stack': inspect.stack()})

            stack_code = stack_frame.f_code
            stack_filename = os.path.basename(stack_code.co_filename)
            stack_lineno = stack_code.co_firstlineno
            stack_caller = stack_code.co_name

        self._update_completer()

        # Form Setup
        self.ui = wpf.LoadComponent(self, StringReader(Console.LAYOUT))
        self.ui.Title = 'RevitPythonWrapper Console'
        self.PreviewKeyDown += self.KeyPressPreview
        self.KeyUp += self.OnKeyUpHandler
        self.is_loaded = False

        # Form Init
        self.ui.tbox.Focus()
        if not context and stack_info:
            self.write_line('Caller: {} [ Line:{}] | File: {}'.format(
                                                              stack_caller,
                                                              stack_lineno,
                                                              stack_filename))
        elif msg:
            self.write_line(msg)
        else:
            self.tbox.Text = Console.CARET

        self.ui.tbox.CaretIndex = len(self.tbox.Text)

        # Vars
        self.history_index = 0
        self.ac_options = defaultdict(int)

        self.ShowDialog()

    def force_quit(self, sender, e):
        raise SystemExit('Force Quit')

    def _update_completer(self):
        # Updates Completer. Used at start, and after each exec loop
        context = self.stack_locals.copy()
        context.update(self.stack_globals)
        # context.update(vars(__builtins__))
        self.completer = Completer(context)

    def get_line(self, index):
        line = self.tbox.GetLineText(index).replace('\r\n', '')
        if line.startswith(Console.CARET):
            line = line[len(Console.CARET):]
        logger.debug('Get Line: {}'.format(line))
        return line

    def get_last_line(self):
        try:
            last_line = self.get_lines()[-1]
        except IndexError:
            last_line = self.get_line(0)
        logger.debug('Last Line: {}'.format(last_line))
        return last_line

    def get_last_entered_line(self):
        try:
            last_line = self.get_lines()[-2]
        except IndexError:
            last_line = self.get_line(0)
        logger.debug('Last Line: {}'.format(last_line))
        return last_line

    def get_lines(self):
        last_line_index = self.tbox.LineCount
        lines = []
        for index in range(0, last_line_index):
            line = self.get_line(index)
            lines.append(line)
        logger.debug('Lines: {}'.format(lines))
        return lines

    def OnKeyUpHandler(self, sender, args):
        # Need to use this to be able to override ENTER
        if not self.is_loaded:
            return
        if args.Key == Key.Enter:
            entered_line = self.get_last_entered_line()
            if entered_line == '':
                self.write_line(None)
                return
            output = self.evaluate(entered_line)
            self.append_history(entered_line)
            self.history_index = 0
            self.write_line(output)
            self.tbox.ScrollToEnd()

    def format_exception(self):
        """ Formats Last Exception"""
        exc_type, exc_value, exc_traceback = sys.exc_info()
        tb = traceback.format_exception(exc_type, exc_value, exc_traceback)
        last_exception = tb[-1]
        output = 'Traceback:\n' + last_exception[:-1]
        return output

    def evaluate(self, line):
        try:
            output = eval(line, self.stack_globals, self.stack_locals)
        except SyntaxError as exc:
            try:
                exec(line, self.stack_globals, self.stack_locals)
                self._update_completer()  # Update completer with new locals
                return
            except Exception as exc:
                output = self.format_exception()
        except Exception as exc:
            output = self.format_exception()
        return str(output)

    def OnKeyDownHandler(self, sender, args):
        pass

    @property
    def last_caret_start_index(self):
        return self.tbox.Text.rfind(Console.CARET)

    @property
    def last_caret_end_index(self):
        return self.last_caret_start_index + len(Console.CARET)

    @property
    def last_caret_line_start_index(self):
        return self.last_caret_start_index - len(Console.CARET)

    def reset_caret(self):
        self.tbox.CaretIndex = self.last_caret_end_index

    def KeyPressPreview(self, sender, e):
        # This Happens before all other key handlers
        # If e.Handled = True, stops event propagation here.
        e.Handled = False
        if self.tbox.CaretIndex < self.last_caret_start_index:
            self.tbox.CaretIndex = len(self.tbox.Text)
        if e.Key == Key.Up:
            self.history_up()
            e.Handled = True
        if e.Key == Key.Down:
            self.history_down()
            e.Handled = True
        if e.Key == Key.Left or e.Key == Key.Back:
            if self.ui.tbox.CaretIndex == self.last_caret_end_index:
                e.Handled = True
        if e.Key == Key.Home:
            self.reset_caret()
            e.Handled = True
        if e.Key == Key.Tab:
            self.autocomplete()
            e.Handled = True
        if e.Key == Key.Enter:
            self.is_loaded = True
            self.tbox.CaretIndex = len(self.tbox.Text)

    def autocomplete(self):
        text = self.tbox.Text[self.last_caret_end_index:self.tbox.CaretIndex]
        logger.debug('Text: {}'.format(text))

        # Create Dictionary to Track iteration over suggestion
        index = self.ac_options[text]
        suggestion = self.completer.complete(text, index)

        logger.debug('ac_options: {}'.format(self.ac_options))
        logger.debug('Sug: {}'.format(suggestion))

        if not suggestion:
            self.ac_options[text] = 0
        else:
            self.ac_options[text] += 1
            if suggestion.endswith('('):
                suggestion = suggestion[:-1]

        if suggestion is not None:
            caret_index = self.tbox.CaretIndex
            self.write_text(suggestion)
            self.tbox.CaretIndex = caret_index

    def write(self, text):
        """ Make Console usable as File Object """
        self.write_line(line=text)

    def write_line(self, line=None):
        # Used for Code Output
        # Writes line with no starting caret, new line + caret
        if line:
            self.tbox.AppendText(line)
            self.tbox.AppendText(NewLine)
        self.tbox.AppendText(Console.CARET)

    def write_text(self, line):
        # Used by Autocomplete and History
        # Adds text to line, including Caret
        self.tbox.Text = self.tbox.Text[0:self.last_caret_start_index]
        self.tbox.AppendText(Console.CARET)
        self.tbox.AppendText(line)
        self.ui.tbox.CaretIndex = len(self.ui.tbox.Text)

    def get_all_history(self):
        # TODO: Add clean up when history > X
        with open(self.history_file) as fp:
            lines = fp.read().split('\n')
            return [line for line in lines if line != '']

    def history_up(self):
        self.history_index += 1
        line = self.history_iter()
        if line is not None:
            self.write_text(line)

    def history_down(self):
        self.history_index -= 1
        line = self.history_iter()
        if line is not None:
            self.write_text(line)

    def append_history(self, line):
        logger.debug('Adding Line to History:' + repr(line))
        with open(self.history_file, 'a') as fp:
            fp.write(line + '\n')

    def history_iter(self):
        lines = self.get_all_history()
        logger.debug('Lines: {}'.format(lines))
        try:
            line = lines[::-1][self.history_index - 1]
        # Wrap around lines to loop and up down infinetly.
        except IndexError:
            if len(lines) == 0:
                return None
            if len(lines) < 0:
                self.history_index += len(lines)
            if len(lines) > 0:
                self.history_index -= len(lines)
            line = lines[0]
        return line

    def __repr__(self):
        '<rpw:Console stack_level={}>'.format(self.stack_level)


if __name__ == '__main__':
    def test():
        x = 1
        # Console()
        Console(context=locals())
    test()
    z = 2

Resources


import sys

from abc import ABCMeta

from rpw import revit
from rpw.utils.dotnet import clr
from rpw.utils.logger import logger

# WPF/Form Imports
clr.AddReference("PresentationFramework")  # System.Windows: Controls, ?
clr.AddReference("WindowsBase")            # System.Windows.Input
clr.AddReference("System.Drawing")         # FontFamily
clr.AddReference('System.Windows.Forms') # Forms

import System.Windows
from System.Windows import Window
from System.IO import StringReader

# Console
from System.Environment import Exit, NewLine
from System.Drawing import FontFamily
from System.Windows.Input import Key

# FlexForm Imports
from System.Windows import Controls, Window
from System.Windows import HorizontalAlignment, VerticalAlignment, Thickness

# OS Dialogs
from System.Windows import Forms

if revit.host == 'Dynamo':
    # IronPython 2.7.3 - Dynamo + RPS w/out pyRevit
    # Conflicts with PyRevit. Must Ensure exact path is specified
    # https://github.com/architecture-building-systems/revitpythonshell/issues/46
    clr.AddReferenceToFileAndPath(r'C:\Program Files (x86)\IronPython 2.7\Platforms\Net40\IronPython.Wpf.dll')
    import wpf
    # on 2.7.7 this raises wpf import error
else:
    # IronPython 2.7.7 - pyRevit
    # clr.AddReference('IronPython')    # Works W/Out
    clr.AddReference('IronPython.Wpf')  # 2.7.
    from IronPython.Modules import Wpf as wpf
    # on 2.7.7 this works. On 2.7.3 you get a LoadComponent 3 args error