How to: Customize Editing and Recurrence Dialogs

  • 4 min to read

This example demonstrates how to replace built-in appointment and recurrence editors with custom windows, which provide additional functionality to meet user requirements.

This tutorial consists of the following sections:

Create a New Application

  1. Create a new WPF Application project and open the MainWindow.xaml file in the Visual Studio Designer.
  2. Add the SchedulerControl object to your project. You can do this by dragging the SchedulerControl item from the DX.19.2: Scheduling Toolbox tab to the canvas.

    WPFScheduler_DragDropFromToolbox

  3. Right-click the SchedulerControl object and select Layout | Reset All in the context menu to stretch the SchedulerControl so that it fills the entire window.

Create a Custom Appointment Editing Window

When the appointment window is invoked, its DataContext is set to the AppointmentWindowViewModel class instance. Create a custom window whose bindings use the properties and commands of the AppointmentWindowViewModel class.

<dx:DXWindow
    x:Class="CustomMvvmFormWithRecurrenceExample.HospitalAppointmentWindow"
    x:Name="AppointmentWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:dxe="http://schemas.devexpress.com/winfx/2008/xaml/editors"
    xmlns:dxsch="http://schemas.devexpress.com/winfx/2008/xaml/scheduling"
    xmlns:dxmvvm="http://schemas.devexpress.com/winfx/2008/xaml/mvvm"
    xmlns:dx="http://schemas.devexpress.com/winfx/2008/xaml/core"
    xmlns:dxlc="http://schemas.devexpress.com/winfx/2008/xaml/layoutcontrol"
    xmlns:dxscht="http://schemas.devexpress.com/winfx/2008/xaml/scheduling/themekeys"
    MinWidth="400"
    MinHeight="350"
    mc:Ignorable="d"
    SizeToContent="WidthAndHeight" Title="{Binding Title}">

    <Window.Resources>
        <dxsch:TimeSpanToDateTimeConverter x:Key="timeSpanToDateTimeConverter" />
    </Window.Resources>

    <dxmvvm:Interaction.Behaviors>
        <dxmvvm:CurrentWindowService ClosingCommand="{Binding WindowClosingCommand}" />
    </dxmvvm:Interaction.Behaviors>

    <DockPanel>
        <dxlc:LayoutControl
            x:Name="validationContainer"
            dxe:ValidationService.IsValidationContainer="True"
            Orientation="Vertical">
            <dxlc:LayoutGroup Orientation="Horizontal">
                <dxlc:LayoutGroup HorizontalAlignment="Stretch" Orientation="Vertical">
                    <!--Subject-->
                    <dxlc:LayoutItem Label="Patient name:">
                        <dxe:TextEdit x:Name="subjectEdit" EditValue="{Binding Subject, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" />
                    </dxlc:LayoutItem>
                    <!--Location-->
                    <dxlc:LayoutItem Label="Office:">
                        <dxe:TextEdit EditValue="{Binding Location, Mode=TwoWay}" />
                    </dxlc:LayoutItem>
                    <!--Start-->
                    <dxlc:LayoutItem Margin="0,10,0,0" Style="{DynamicResource {dxscht:AppointmentWindowThemeKey ResourceKey=LayoutItem_StartTime}}">
                        <DockPanel>
                            <dxe:DateEdit
                                x:Name="editorStartDate"
                                Width="150"
                                DockPanel.Dock="Left"
                                Style="{DynamicResource {dxscht:AppointmentWindowThemeKey ResourceKey=Editor_StartDate}}" />
                            <dxe:TextEdit
                                x:Name="editorStartTime"
                                Margin="4,0,0,0"
                                DockPanel.Dock="Left"
                                Style="{DynamicResource {dxscht:AppointmentWindowThemeKey ResourceKey=Editor_StartTime}}" />
                        </DockPanel>
                    </dxlc:LayoutItem>
                    <!--End-->
                    <dxlc:LayoutItem Style="{DynamicResource {dxscht:AppointmentWindowThemeKey ResourceKey=LayoutItem_EndTime}}">
                        <DockPanel>
                            <dxe:DateEdit
                                x:Name="editorEndDate"
                                Width="150"
                                DockPanel.Dock="Left"
                                Style="{DynamicResource {dxscht:AppointmentWindowThemeKey ResourceKey=Editor_EndDate}}" />
                            <dxe:TextEdit
                                x:Name="editorEndTime"
                                Margin="4,0,0,0"
                                DockPanel.Dock="Left"
                                Style="{DynamicResource {dxscht:AppointmentWindowThemeKey ResourceKey=Editor_EndTime}}" />
                        </DockPanel>
                    </dxlc:LayoutItem>
                    <!--Insurance number (custom field)-->
                    <dxlc:LayoutItem Label="Insurance number:">
                        <dxe:TextEdit EditValue="{Binding CustomFields.InsuranceNumber, Mode=TwoWay}" />
                    </dxlc:LayoutItem>
                    <!--First visit (custom field)-->
                    <dxlc:LayoutItem Label="First visit:">
                        <dxe:CheckEdit EditValue="{Binding CustomFields.FirstVisit, Mode=TwoWay}" />
                    </dxlc:LayoutItem>
                    <!--Resource's ID-->
                    <dxlc:LayoutItem Label="Doctor:">
                        <dxsch:AppointmentResourceEdit
                            EditValue="{Binding Resource, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
                            Scheduler="{Binding Path='(dxsch:SchedulerControl.Scheduler)', RelativeSource={RelativeSource Self}}"
                            ShowEmptyResource="False">
                            <dxmvvm:Interaction.Behaviors>
                                <dxmvvm:FocusBehavior />
                            </dxmvvm:Interaction.Behaviors>
                        </dxsch:AppointmentResourceEdit>
                    </dxlc:LayoutItem>
                    <!--Notes (custom field)-->
                    <dxlc:LayoutItem Label="Notes:">
                        <dxe:TextEdit
                            MinHeight="50"
                            HorizontalContentAlignment="Left"
                            VerticalContentAlignment="Top"
                            AcceptsReturn="True"
                            EditValue ="{Binding CustomFields.Notes, Mode=TwoWay}"
                            TextWrapping="Wrap"
                            VerticalScrollBarVisibility="Visible" />
                    </dxlc:LayoutItem>
                </dxlc:LayoutGroup>
            </dxlc:LayoutGroup>
            <!--Buttons-->
            <StackPanel
                Height="20"
                Margin="0,6,0,0"
                HorizontalAlignment="Right"
                Orientation="Horizontal">
                <Button
                    x:Name="btnOk"
                    MinWidth="75"
                    Margin="6,0,0,0"
                    Command="{Binding SaveAndCloseAppointmentCommand}"
                    Content="OK" />
                <Button
                    x:Name="btnRecurrence"
                    MinWidth="75"
                    Margin="6,0,0,0"
                    Command="{Binding EditRecurrenceCommand}"
                    Content="Recurrence" />
            </StackPanel>
        </dxlc:LayoutControl>
    </DockPanel>

</dx:DXWindow>

Create a Custom Recurrence Window

Create a custom window whose bindings use the properties and commands of the RecurrenceWindowViewModel class.

    <dx:DXDialogWindow
    x:Class="CustomMvvmFormWithRecurrenceExample.HospitalRecurrenceWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:dx="http://schemas.devexpress.com/winfx/2008/xaml/core"
    xmlns:dxsch="http://schemas.devexpress.com/winfx/2008/xaml/scheduling"
    xmlns:dxec="http://schemas.devexpress.com/winfx/2008/xaml/editors/"
    xmlns:dxmvvm="http://schemas.devexpress.com/winfx/2008/xaml/mvvm"
    Title="Recurrence Window"  Width="368.061" Height="380" MinWidth="450" MinHeight="380" ResizeMode="NoResize">

    <dx:DXDialogWindow.Resources>
        <Style x:Key="recurrenceInfoControlStyle" TargetType="dxsch:RecurrenceInfoControlBase">
            <Setter Property="RecurrenceInfo" Value="{Binding RecurrenceInfo}"/>
            <Style.Triggers>
                <Trigger Property="Visibility" Value="Collapsed">
                    <Setter Property="RecurrenceInfo" Value="{x:Null}"/>
                </Trigger>
            </Style.Triggers>
        </Style>
    </dx:DXDialogWindow.Resources>

    <Grid Margin="12">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto" />
            <ColumnDefinition Width="Auto" />
            <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>
        <!--Recurrence Pattern Group-->
        <GroupBox Header="Recurrence Pattern" Margin="0,12,0,0"  Grid.ColumnSpan="3">
            <DockPanel>
                <dxsch:RecurrenceTypeControl DockPanel.Dock="Left" RecurrenceTypes="{Binding AvailableRecurrenceTypes}" RecurrenceType="{Binding RecurrenceInfo.Type, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
                <StackPanel Orientation="Horizontal">
                    <Separator Style="{StaticResource {x:Static ToolBar.SeparatorStyleKey}}" />
                </StackPanel>
                <StackPanel MinHeight="90">
                    <dxsch:DailyRecurrenceControl Style="{StaticResource recurrenceInfoControlStyle}" Visibility="{Binding RecurrenceInfo.Type, Converter={dxsch:RecurrenceTypeToVisibilityConverter VisibleValue=Daily}}"/>
                    <dxsch:WeeklyRecurrenceControl Style="{StaticResource recurrenceInfoControlStyle}" Visibility="{Binding RecurrenceInfo.Type, Converter={dxsch:RecurrenceTypeToVisibilityConverter VisibleValue=Weekly}}"
                                                    FirstDayOfWeek="{Binding FirstDayOfWeek}"/>
                    <dxsch:MonthlyRecurrenceControl Style="{StaticResource recurrenceInfoControlStyle}" Visibility="{Binding RecurrenceInfo.Type, Converter={dxsch:RecurrenceTypeToVisibilityConverter VisibleValue=Monthly}}"/>
                    <dxsch:YearlyRecurrenceControl x:Name="yearlyRecurrence" Style="{StaticResource recurrenceInfoControlStyle}" Visibility="{Binding RecurrenceInfo.Type, Converter={dxsch:RecurrenceTypeToVisibilityConverter VisibleValue=Yearly}}"/>
                </StackPanel>
            </DockPanel>
        </GroupBox>
        <!--Recurrence End Group-->
        <GroupBox Grid.Row="1" Grid.ColumnSpan="3" Header="Recurrence End" Margin="0,12,0,0">
            <dxsch:RecurrenceRangeControl RecurrenceInfo="{Binding RecurrenceInfo}"/>
        </GroupBox>
    </Grid>
    <dx:DXDialogWindow.FooterButtons>
        <dx:DialogButton Content="{dxsch:SchedulerLocalizer StringId=ButtonCaption_OK}" Command="{Binding SaveCommand}" IsDefault="True" MinWidth="65" DialogResult="OK"/>
        <dx:DialogButton Content="{dxsch:SchedulerLocalizer StringId=ButtonCaption_Cancel}" IsCancel="True" MinWidth="65" DialogResult="Cancel"/>
        <dx:DialogButton Content="{dxsch:SchedulerLocalizer StringId=ButtonCaption_Delete}" Command="{Binding DeleteCommand}" MinWidth="65" DialogResult="Cancel"/>
    </dx:DXDialogWindow.FooterButtons>
</dx:DXDialogWindow>

Replace Window Types

Set the OptionsWindows.AppointmentWindowType value to the type of a custom appointment editing window (HospitalAppointmentWindow). Set the OptionsWindows.RecurrenceWindowType value to the type of a custom recurrence window (HospitalRecurrenceWindow).

<dxsch:SchedulerControl.OptionsWindows>
    <dxsch:OptionsWindows AppointmentWindowType="{x:Type local:HospitalAppointmentWindow}" RecurrenceWindowType="{x:Type local:HospitalRecurrenceWindow}" />
</dxsch:SchedulerControl.OptionsWindows>
TIP

You can also handle the SchedulerControl.AppointmentWindowShowing event to display a custom window.

Create the AppointmentWindowViewModel Descendant

You can modify the window's View Model behavior by creating the AppointmentWindowViewModel class descendant as illustrated in the code snippet below:

public class AppointmentWindowViewModelEx : AppointmentWindowViewModel {
    public AppointmentWindowViewModelEx(AppointmentItem appointmentItem, SchedulerControl scheduler) : base(appointmentItem, scheduler) { }
    //...
}

To use the custom AppointmentWindowViewModelEx type in the appointment window, handle the AppointmentWindowShowing event and set the e.Window.DataContext property as follows:

private void Scheduler_AppointmentWindowShowing(object sender, DevExpress.Xpf.Scheduling.AppointmentWindowShowingEventArgs e) {
    e.Window.DataContext = new AppointmentWindowViewModelEx(e.Appointment, (SchedulerControl)sender);
}

You can use this approach if you specify the window type by setting the AppointmentWindowType property.

Run the Application and Observe the Result

DXScheduler_Examples_CustomForm