Search Results

Search found 1978 results on 80 pages for 'jane story'.

Page 80/80 | < Previous Page | 76 77 78 79 80 

  • How do I send automated e-mails from Drupal using Messaging and Notifications?

    - by Adrian
    I am working on a Notifications plugin, and after starting to write my notes down about how to do this, decided to just post them here. Please feel free to come make modifications and changes. Eventually I hope to post this on the Drupal handbook as well. Thanks. --Adrian Sending automated e-mails from Drupal using Messaging and Notifications To implement a notifications plugin, you must implement the following functions: Use hook_messaging, hook_token_list and hook_token_values to create the messages that will be sent. Use hook_notifications to create the subscription types Add code to fire events (eg in hook_nodeapi) Add all UI elements to allow users to subscribe/unsubscribe Understanding Messaging The Messaging module is used to compose messages that can be delivered using various formats, such as simple mail, HTML mail, Twitter updates, etc. These formats are called "send methods." The backend details do not concern us here; what is important are the following concepts: TOKENS: tokens are provided by the "tokens" module. They allow you to write keywords in square brackets, [like-this], that can be replaced by any arbitrary value. Note: the token groups you create must match the keys you add to the $events-objects[$key] array. MESSAGE KEYS: A key is a part of a message, such as the greetings line. Keys can be different for each send method. For example, a plaintext mail's greeting might be "Hi, [user]," while an HTML greeing might be "Hi, [user]," and Twitter's might just be "[user-firstname]: ". Keys can have any arbitrary name. Keys are very simple and only have a machine-readable name and a user-readable description, the latter of which is only seen by admins. MESSAGE GROUPS: A group is a bunch of keys that often, but not always, might be used together to make up a complete message. For example, a generic group might include keys for a greeting, body, closing and footer. Groups can also be "subclassed" by selecting a "fallback" group that will supply any keys that are missing. Groups are also associated with modules; I'm not sure what these are used for. Understanding Notifications The Notifications module revolves around the following concepts: SUBSCRIPTIONS: Notifications plugins may define one or more types of subscriptions. For example, notifications_content defines subscriptions for: Threads (users are notified whenever a node or its comments change) Content types (users are notified whenever a node of a certain type is created or is changed) Users (users are notified whenever another user is changed) Subscriptions refer to both the user who's subscribed, how often they wish to be notified, the send method (for Messaging) and what's being subscribed to. This last part is defined in two steps. Firstly, a plugin defines several "subscription fields" (through a hook_notifications op of the same name), and secondly, "subscription types" (also an op) defines which fields apply to each type of subscription. For example, notifications_content defines the fields "nid," "author" and "type," and the subscriptions "thread" (nid), "nodetype" (type), "author" (author) and "typeauthor" (type and author), the latter referring to something like "any STORY by JOE." Fields are used to link events to subscriptions; an event must match all fields of a subscription (for all normal subscriptions) to be delivered to the recipient. The $subscriptions object is defined in subsequent sections. Notifications prefers that you don't create these objects yourself, preferring you to call the notifications_get_link() function to create a link that users may click on, but you can also use notifications_save_subscription and notifications_delete_subscription to do it yourself. EVENTS: An event is something that users may be notified about. Plugins create the $event object then call notifications_event($event). This either sends out notifications immediately, queues them to send out later, or both. Events include the type of thing that's changed (eg 'node', 'user'), the ID of the thing that's changed (eg $node-nid, $user-uid) and what's happened to it (eg 'create'). These are, respectively, $event-type, $event-oid (object ID) and $event-action. Warning: notifications_content_nodeapi also adds a $event-node field, referring to the node itself and not just $event-oid = $node-nid. This is not used anywhere in the core notifications module; however, when the $event is passed back to the 'query' op (see below), we assume the node is still present. Events do not refer to the user they will be referred to; instead, Notifications makes the connection between subscriptions and events, using the subscriptions' fields. MATCHING EVENTS TO SUBSCRIPTIONS: An event matches a subscription if it has the same type as the event (eg "node") and if the event matches all the correct fields. This second step is determined by the "query" hook op, which is called with the $event object as a parameter. The query op is responsible for giving Notifications a value for all the fields defined by the plugin. For example, notifications_content defines the 'nid', 'type' and 'author' fields, so its query op looks like this (ignore the case where $event_or_user = 'user' for now): $event_or_user = $arg0; $event_type = $arg1; $event_or_object = $arg2; if ($event_or_user == 'event' && $event_type == 'node' && ($node = $event_or_object->node) || $event_or_user == 'user' && $event_type == 'node' && ($node = $event_or_object)) { $query[]['fields'] = array( 'nid' => $node->nid, 'type' => $node->type, 'author' => $node->uid, ); return $query; After extracting the $node from the $event, we set $query[]['fields'] to a dictionary defining, for this event, all the fields defined by the module. As you can tell from the presence of the $query object, there's way more you can do with this op, but they are not covered here. DIGESTING AND DEDUPING: Understanding the relationship between Messaging and Notifications Usually, the name of a message group doesn't matter, but when being used with Notifications, the names must follow very strict patterns. Firstly, they must start with the name "notifications," and then are followed by either "event" or "digest," depending on whether the message group is being used to represent either a single event or a group of events. For 'events,' the third part of the name is the "type," which we get from Notification's $event-type (eg: notifications_content uses 'node'). The last part of the name is the operation being performed, which comes from Notification's $event-action. For example: notifications-event-node-comment might refer to the message group used when someone comments on a node notifications-event-user-update to a user who's updated their profile Hyphens cannot appear anywhere other than to separate the parts of these words. For 'digest' messages, the third and fourth part of the name come from hook_notification's "event types" callback, specifically this line: $types[] = array( 'type' => 'node', 'action' => 'insert', ... 'digest' => array('node', 'type'), ); $types[] = array( 'type' => 'node', 'action' => 'update', ... 'digest' => array('node', 'nid'), ); In this case, the first event type (node insertion) will be digested with the notifications-digest-node-type message template providing the header and footer, likely saying something like "the following [type] was created." The second event type (node update) will be digested with the notifications-digest-node-nid message template. Data Structure and Callback Reference $event The $event object has the following members: $event-type: The type of event. Must match the type in hook_notification::"event types". {notifications_event} $event-action: The action the event describes. Most events are sorted by [$event-type][$event-action]. {notifications_event}. $event-object[$object_type]: All objects relevant to the event. For example, $event-object['node'] might be the node that the event describes. $object_type can come from the 'event types' hook (see below). The main purpose appears to be to be passed to token_replace_multiple as the second parameter. $event-object[$event-type] is assumed to exist in the short digest processing functions, but this doesn't appear to be used anywhere. Not saved in the database; loaded by hook_notifications::"event load" $event-oid: apparently unused. The id of the primary object relevant to this event (eg the node's nid). $event-module: apparently unused $event-params[$key]: Mainly a place for plugins to save random data. The main module will serialize the contents of this array but does not use it in any way. However, notifications_ui appears to do something weird with it, possibly by using subscriptions' fields as keys into this array. I'm not sure why though. hook_notifications op 'subscription types': returns an array of subscription types provided by the plugin, in the form $key = array(...) with the following members: event_type: this subscription can only match events whose $event-type has this value. Stored in the database as notifications.event_type for every individual subscription. Apparently, this can be overiden in code but I wouldn't try it (see notifications_save_subscription). fields: an unkeyed array of fields that must be matched by an event (in addition to the event_type) for it to match this subscription. Each element of this array must be a key of the array returned by op 'subscription fields' which in turn must be used by op 'query' to actually perform the matching. title: user-readable title for their subscriptions page (eg the 'type' column in user/%uid/notifications/subscriptions) description: a user-readable description. page callback: used to add a supplementary page at user/%uid/notifications/blah. This and the following are used by notifications_ui as a part of hook_menu_alter. Appears to be partially deprecated. user page: user/%uid/notifications/blah. op 'event types': returns an array of event types, with each event type being an array with the following members: type: this will match $event-type action: this will match $event-action digest: an array with two ordered (non-keyed) elements, "type" and "field." 'type' is used as an index into $event-objects. 'field' is also used to group events like so: $event-objects[$type]-$field. For example, 'field' might be 'nid' - if the object is a node, the digest lines will be grouped by node ID. Finally, both are used to find the correct Messaging template; see discussion above. description: used on the admin "Notifications-Events" page name: unused, use Messaging instead line: deprecated, use Messaging instead Other Stuff This is an example of the main query that inserts an event into the queue: INSERT INTO {notifications_queue} (uid, destination, sid, module, eid, send_interval, send_method, cron, created, conditions) SELECT DISTINCT s.uid, s.destination, s.sid, s.module, %d, // event ID s.send_interval, s.send_method, s.cron, %d, // time of the event s.conditions FROM {notifications} s INNER JOIN {notifications_fields} f ON s.sid = f.sid WHERE (s.status = 1) AND (s.event_type = '%s') // subscription type AND (s.send_interval >= 0) AND (s.uid <> %d) AND ( (f.field = '%s' AND f.intval IN (%d)) // everything from 'query' op OR (f.field = '%s' AND f.intval = %d) OR (f.field = '%s' AND f.value = '%s') OR (f.field = '%s' AND f.intval = %d)) GROUP BY s.uid, s.destination, s.sid, s.module, s.send_interval, s.send_method, s.cron, s.conditions HAVING s.conditions = count(f.sid)

    Read the article

  • Silverlight Recruiting Application Part 6 - Adding an Interview Scheduling Module/View

    Between the last post and this one I went ahead and carried the ideas for the Jobs module and view into the Applicants module and view- they're both doing more or less the same thing, except with different objects being at their core.  Made for an easy cut-and-paste operation with a few items being switched from one to another.  Now that we have the ability to add postings and applicants, wouldn't it be nice if we could schedule an interview?  Of course it would! Scheduling Module I think you get the drift from previous posts that these project structures start looking somewhat similar.  The interview scheduling module is no different than the rest- it gets a SchedulingModule.cs file at the root that inherits from IModule, and there is a single SchedulerView.xsml and SchedulerViewModel.cs setup for our V+VM.  We have one unique concern as we enter into this- RadScheduler deals with AppointmentsSource, not ItemsSource, so there are some special considerations to take into account when planning this module. First, I need something which inherits from AppointmentBase.  This is the core of the RadScheduler appointment, and if you are planning to do any form of custom appointment, you'll want it to inherit from this.  Then you can add-on functionality as needed.  Here is my addition to the mix, the InterviewAppointment: 01.public class InterviewAppointment : AppointmentBase 02.{ 03.    private int _applicantID; 04.    public int ApplicantID 05.    { 06.        get { return this._applicantID; } 07.        set 08.        { 09.            if (_applicantID != value) 10.            { 11.                _applicantID = value; 12.                OnPropertyChanged("ApplicantID"); 13.            } 14.        } 15.    } 16.   17.    private int _postingID; 18.    public int PostingID 19.    { 20.        get { return _postingID; } 21.        set 22.        { 23.            if (_postingID != value) 24.            { 25.                _postingID = value; 26.                OnPropertyChanged("PostingID"); 27.            } 28.        } 29.    } 30.   31.    private string _body; 32.    public string Body 33.    { 34.        get { return _body; } 35.        set 36.        { 37.            if (_body != value) 38.            { 39.                _body = value; 40.                OnPropertyChanged("Body"); 41.            } 42.        } 43.    } 44.   45.    private int _interviewID; 46.    public int InterviewID 47.    { 48.        get { return _interviewID; } 49.        set 50.        { 51.            if (_interviewID != value) 52.            { 53.                _interviewID = value; 54.                OnPropertyChanged("InterviewID"); 55.            } 56.        } 57.    } 58.   59.    public override IAppointment Copy() 60.    { 61.        IAppointment appointment = new InterviewAppointment(); 62.        appointment.CopyFrom(this);             63.        return appointment; 64.    } 65.   66.    public override void CopyFrom(IAppointment other) 67.    {             68.        base.CopyFrom(other); 69.        var appointment = other as InterviewAppointment; 70.        if (appointment != null) 71.        { 72.            ApplicantID = appointment.ApplicantID; 73.            PostingID = appointment.PostingID; 74.            Body = appointment.Body; 75.            InterviewID = appointment.InterviewID; 76.        } 77.    } 78.} Nothing too exciting going on here, we just make sure that our custom fields are persisted (specifically set in CopyFrom at the bottom) and notifications are fired- otherwise this ends up exactly like the standard appointment as far as interactions, etc.  But if we've got custom appointment items... that also means we need to customize what our appointment dialog window will look like. Customizing the Edit Appointment Dialog This initially sounds a lot more intimidating than it really is.  The first step here depends on what you're dealing with for theming, but for ease of everything I went ahead and extracted my templates in Blend for RadScheduler so I could modify it as I pleased.  For the faint of heart, the RadScheduler template is a few thousand lines of goodness since there are some very complex things going on in that control.  I've gone ahead and trimmed down the template parts I don't need as much as possible, so what is left is all that is relevant to the Edit Appointment Dialog.  Here's the resulting Xaml, with line numbers, so I can explain further: 001.<UserControl.Resources> 002.    <!-- begin Necessary Windows 7 Theme Resources for EditAppointmentTemplate --> 003.    <helpers:DataContextProxy x:Key="DataContextProxy" /> 004.       005.    <telerik:Windows7Theme x:Key="Theme" /> 006.    <SolidColorBrush x:Key="DialogWindowBackground" 007.                     Color="White" /> 008.    <SolidColorBrush x:Key="CategorySelectorBorderBrush" 009.                     Color="#FFB1B1B1" /> 010.    <LinearGradientBrush x:Key="RadToolBar_InnerBackground" 011.                         EndPoint="0.5,1" 012.                         StartPoint="0.5,0"> 013.        <GradientStop Color="#FFFDFEFF" 014.                      Offset="0" /> 015.        <GradientStop Color="#FFDDE9F7" 016.                      Offset="1" /> 017.        <GradientStop Color="#FFE6F0FA" 018.                      Offset="0.5" /> 019.        <GradientStop Color="#FFDCE6F4" 020.                      Offset="0.5" /> 021.    </LinearGradientBrush> 022.    <Style x:Key="FormElementTextBlockStyle" 023.           TargetType="TextBlock"> 024.        <Setter Property="HorizontalAlignment" 025.                Value="Right" /> 026.        <Setter Property="VerticalAlignment" 027.                Value="Top" /> 028.        <Setter Property="Margin" 029.                Value="15, 15, 0, 2" /> 030.    </Style> 031.    <Style x:Key="FormElementStyle" 032.           TargetType="FrameworkElement"> 033.        <Setter Property="Margin" 034.                Value="10, 10, 0, 2" /> 035.    </Style> 036.    <SolidColorBrush x:Key="GenericShallowBorderBrush" 037.                     Color="#FF979994" /> 038.    <telerik:BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" /> 039.    <telerikScheduler:ImportanceToBooleanConverter x:Key="ImportanceToBooleanConverter" /> 040.    <telerikScheduler:NullToVisibilityConverter x:Key="NullToVisibilityConverter" /> 041.    <telerikScheduler:InvertedNullToVisibilityConverter x:Key="InvertedNullToVisibilityConverter" /> 042.    <scheduler:ResourcesSeparatorConverter x:Key="ResourcesSeparatorConverter" /> 043.    <DataTemplate x:Key="IconDataEditTemplate"> 044.        <Image Source="/Telerik.Windows.Controls.Scheduler;component/Themes/Office/Images/cal.png" 045.               Margin="3,3,0,0" 046.               Width="16" 047.               Height="16" /> 048.    </DataTemplate> 049.    <DataTemplate x:Key="SingleSelectionTemplate"> 050.        <Grid VerticalAlignment="Stretch" 051.              HorizontalAlignment="Stretch"> 052.            <Grid.RowDefinitions> 053.                <RowDefinition Height="Auto" /> 054.            </Grid.RowDefinitions> 055.            <Grid.ColumnDefinitions> 056.                <ColumnDefinition Width="Auto" 057.                                  MinWidth="84" /> 058.                <ColumnDefinition Width="Auto" 059.                                  MinWidth="200" /> 060.            </Grid.ColumnDefinitions> 061.            <TextBlock x:Name="SelectionNameLabel" 062.                       Margin="0,13,4,2" 063.                       Text="{Binding ResourceType.DisplayName}" 064.                       Style="{StaticResource FormElementTextBlockStyle}" 065.                       Grid.Column="0" /> 066.            <telerikInput:RadComboBox ItemsSource="{Binding ResourceItems}" 067.                                      Width="185" 068.                                      Margin="5,10,20,2" 069.                                      HorizontalAlignment="Left" 070.                                      Grid.Column="1" 071.                                      ClearSelectionButtonVisibility="Visible" 072.                                      ClearSelectionButtonContent="Clear All" 073.                                      DisplayMemberPath="Resource.DisplayName" 074.                                      telerik:StyleManager.Theme="{StaticResource Theme}" 075.                                      SelectedItem="{Binding SelectedItem, Mode=TwoWay}" /> 076.        </Grid> 077.    </DataTemplate> 078.    <DataTemplate x:Key="MultipleSelectionTemplate"> 079.        <Grid VerticalAlignment="Stretch" 080.              HorizontalAlignment="Stretch"> 081.            <Grid.RowDefinitions> 082.                <RowDefinition Height="Auto" /> 083.            </Grid.RowDefinitions> 084.            <Grid.ColumnDefinitions> 085.                <ColumnDefinition Width="Auto" 086.                                  MinWidth="84" /> 087.                <ColumnDefinition Width="Auto" 088.                                  MinWidth="200" /> 089.            </Grid.ColumnDefinitions> 090.            <TextBlock x:Name="SelectionNameLabel" 091.                       Grid.Column="0" 092.                       Text="{Binding ResourceType.DisplayName}" 093.                       Margin="0,13,4,2" 094.                       Style="{StaticResource FormElementTextBlockStyle}" /> 095.            <telerikInput:RadComboBox Grid.Column="1" 096.                                      Width="185" 097.                                      HorizontalAlignment="Left" 098.                                      Margin="5,10,20,2" 099.                                      ItemsSource="{Binding ResourceItems}" 100.                                      SelectedIndex="{Binding SelectedIndex, Mode=TwoWay}" 101.                                      ClearSelectionButtonVisibility="Visible" 102.                                      ClearSelectionButtonContent="Clear All" 103.                                      telerik:StyleManager.Theme="{StaticResource Theme}"> 104.                <telerikInput:RadComboBox.ItemTemplate> 105.                    <DataTemplate> 106.                        <Grid HorizontalAlignment="Stretch" 107.                              VerticalAlignment="Stretch"> 108.                            <CheckBox VerticalAlignment="Center" 109.                                      HorizontalContentAlignment="Stretch" 110.                                      VerticalContentAlignment="Center" 111.                                      IsChecked="{Binding IsChecked, Mode=TwoWay}" 112.                                      Content="{Binding Resource.DisplayName}"> 113.                                <CheckBox.ContentTemplate> 114.                                    <DataTemplate> 115.                                        <TextBlock HorizontalAlignment="Stretch" 116.                                                   VerticalAlignment="Stretch" 117.                                                   Text="{Binding Content, RelativeSource={RelativeSource TemplatedParent}}" /> 118.                                    </DataTemplate> 119.                                </CheckBox.ContentTemplate> 120.                            </CheckBox> 121.                        </Grid> 122.                    </DataTemplate> 123.                </telerikInput:RadComboBox.ItemTemplate> 124.            </telerikInput:RadComboBox> 125.        </Grid> 126.    </DataTemplate> 127.    <scheduler:ResourceTypeTemplateSelector x:Key="ItemTemplateSelector" 128.                                            MultipleSelectionTemplate="{StaticResource MultipleSelectionTemplate}" 129.                                            SingleSelectionTemplate="{StaticResource SingleSelectionTemplate}" /> 130.    <!-- end Necessary Windows 7 Theme Resources for EditAppointmentTemplate -->  131.       132.    <ControlTemplate x:Key="EditAppointmentTemplate" 133.                     TargetType="telerikScheduler:AppointmentDialogWindow"> 134.        <StackPanel Background="{TemplateBinding Background}" 135.                    UseLayoutRounding="True"> 136.            <StackPanel Grid.Row="0" 137.                        Orientation="Horizontal" 138.                        Background="{StaticResource RadToolBar_InnerBackground}" 139.                        Grid.ColumnSpan="2" 140.                        Height="0"> 141.                <!-- Recurrence buttons --> 142.                <Border Margin="1,1,0,0" 143.                        Background="#50000000" 144.                        HorizontalAlignment="Left" 145.                        VerticalAlignment="Center" 146.                        Width="2" 147.                        Height="16"> 148.                    <Border Margin="0,0,1,1" 149.                            Background="#80FFFFFF" 150.                            HorizontalAlignment="Left" 151.                            Width="1" /> 152.                </Border> 153.                <Border Margin="1,1,0,0" 154.                        Background="#50000000" 155.                        HorizontalAlignment="Left" 156.                        VerticalAlignment="Center" 157.                        Width="2" 158.                        Height="16"> 159.                    <Border Margin="0,0,1,1" 160.                            Background="#80FFFFFF" 161.                            HorizontalAlignment="Left" 162.                            Width="1" /> 163.                </Border> 164.                <TextBlock telerik:LocalizationManager.ResourceKey="ShowAs" 165.                           VerticalAlignment="Center" 166.                           Margin="5,0,0,0" /> 167.                <telerikInput:RadComboBox ItemsSource="{TemplateBinding TimeMarkers}" 168.                                          Width="100" 169.                                          Height="20" 170.                                          VerticalAlignment="Center" 171.                                          Margin="5,0,0,0" 172.                                          ClearSelectionButtonVisibility="Visible" 173.                                          ClearSelectionButtonContent="Clear" 174.                                          SelectedItem="{Binding TimeMarker,RelativeSource={RelativeSource TemplatedParent},Mode=TwoWay}" 175.                                          telerik:StyleManager.Theme="{StaticResource Theme}"> 176.                    <telerikInput:RadComboBox.ItemTemplate> 177.                        <DataTemplate> 178.                            <StackPanel Orientation="Horizontal"> 179.                                <Rectangle Fill="{Binding TimeMarkerBrush}" 180.                                           Margin="2" 181.                                           Width="12" 182.                                           Height="12" /> 183.                                <TextBlock Text="{Binding TimeMarkerName}" 184.                                           Margin="2" /> 185.                            </StackPanel> 186.                        </DataTemplate> 187.                    </telerikInput:RadComboBox.ItemTemplate> 188.                </telerikInput:RadComboBox> 189.                <telerik:RadToggleButton x:Name="High" 190.                                         BorderThickness="0" 191.                                         Background="{StaticResource RadToolBar_InnerBackground}" 192.                                         DataContext="{TemplateBinding EditedAppointment}" 193.                                         telerik:StyleManager.Theme="{StaticResource Theme}" 194.                                         IsChecked="{Binding Importance,Mode=TwoWay, Converter={StaticResource ImportanceToBooleanConverter},ConverterParameter=High}" 195.                                         Margin="2,2,0,2" 196.                                         Width="23" 197.                                         Height="23" 198.                                         HorizontalContentAlignment="Center" 199.                                         ToolTipService.ToolTip="High importance" 200.                                         CommandParameter="High" 201.                                         Command="telerikScheduler:RadSchedulerCommands.SetAppointmentImportance"> 202.                    <StackPanel HorizontalAlignment="Center"> 203.                        <Path Stretch="Fill" 204.                              Height="10" 205.                              HorizontalAlignment="Center" 206.                              VerticalAlignment="Top" 207.                              Width="5.451" 208.                              Data="M200.39647,58.840393 C200.39337,58.336426 201.14566,57.683922 202.56244,57.684292 C204.06589,57.684685 204.73764,58.357765 204.72783,58.992363 C205.04649,61.795574 203.04713,64.181099 202.47388,66.133446 C201.93753,64.154961 199.9471,61.560352 200.39647,58.840393 z"> 209.                            <Path.Fill> 210.                                <LinearGradientBrush EndPoint="1.059,0.375" 211.                                                     StartPoint="-0.457,0.519"> 212.                                    <GradientStop Color="#FFFF0606" 213.                                                  Offset="0.609" /> 214.                                    <GradientStop Color="#FFBF0303" 215.                                                  Offset="0.927" /> 216.                                </LinearGradientBrush> 217.                            </Path.Fill> 218.                        </Path> 219.                        <Ellipse Height="3" 220.                                 HorizontalAlignment="Center" 221.                                 Margin="0,-1,0,0" 222.                                 VerticalAlignment="Top" 223.                                 Width="3"> 224.                            <Ellipse.Fill> 225.                                <RadialGradientBrush> 226.                                    <GradientStop Color="#FFFF0606" 227.                                                  Offset="0" /> 228.                                    <GradientStop Color="#FFBF0303" 229.                                                  Offset="1" /> 230.                                </RadialGradientBrush> 231.                            </Ellipse.Fill> 232.                        </Ellipse> 233.                    </StackPanel> 234.                </telerik:RadToggleButton> 235.                <telerik:RadToggleButton x:Name="Low" 236.                                         HorizontalContentAlignment="Center" 237.                                         BorderThickness="0" 238.                                         Background="{StaticResource RadToolBar_InnerBackground}" 239.                                         DataContext="{TemplateBinding EditedAppointment}" 240.                                         IsChecked="{Binding Importance,Mode=TwoWay, Converter={StaticResource ImportanceToBooleanConverter},ConverterParameter=Low}" 241.                                         Margin="0,2,0,2" 242.                                         Width="23" 243.                                         Height="23" 244.                                         ToolTipService.ToolTip="Low importance" 245.                                         CommandParameter="Low" 246.                                         telerik:StyleManager.Theme="{StaticResource Theme}" 247.                                         Command="telerikScheduler:RadSchedulerCommands.SetAppointmentImportance"> 248.                    <Path Stretch="Fill" 249.                          Height="12" 250.                          HorizontalAlignment="Center" 251.                          VerticalAlignment="Top" 252.                          Width="9" 253.                          Data="M222.40353,60.139881 L226.65768,60.139843 L226.63687,67.240196 L229.15347,67.240196 L224.37816,71.394943 L219.65274,67.240196 L222.37572,67.219345 z" 254.                          Stroke="#FF0365A7"> 255.                        <Path.Fill> 256.                            <LinearGradientBrush EndPoint="1.059,0.375" 257.                                                 StartPoint="-0.457,0.519"> 258.                                <GradientStop Color="#FFBBE4FF" /> 259.                                <GradientStop Color="#FF024572" 260.                                              Offset="0.836" /> 261.                                <GradientStop Color="#FF43ADF4" 262.                                              Offset="0.466" /> 263.                            </LinearGradientBrush> 264.                        </Path.Fill> 265.                    </Path> 266.                </telerik:RadToggleButton> 267.            </StackPanel > 268.            <Border DataContext="{TemplateBinding EditedAppointment}" 269.                    Background="{Binding Category.CategoryBrush}" 270.                    Visibility="{Binding Category,Converter={StaticResource NullToVisibilityConverter}}" 271.                    CornerRadius="3" 272.                    Height="20" 273.                    Margin="5,10,5,0"> 274.                <TextBlock Text="{Binding Category.DisplayName}" 275.                           VerticalAlignment="Center" 276.                           Margin="5,0,0,0" /> 277.            </Border> 278.            <Grid VerticalAlignment="Stretch" 279.                  HorizontalAlignment="Stretch" 280.                  DataContext="{TemplateBinding EditedAppointment}" 281.                  Background="{TemplateBinding Background}"> 282.                <Grid.RowDefinitions> 283.                    <RowDefinition Height="Auto" /> 284.                    <RowDefinition Height="Auto" /> 285.                    <RowDefinition Height="Auto" /> 286.                    <RowDefinition Height="Auto" /> 287.                    <RowDefinition Height="Auto" /> 288.                    <RowDefinition Height="Auto" /> 289.                    <RowDefinition Height="Auto" /> 290.                    <RowDefinition Height="Auto" /> 291.                    <RowDefinition Height="Auto" /> 292.                    <RowDefinition Height="Auto" /> 293.                </Grid.RowDefinitions> 294.                <Grid.ColumnDefinitions> 295.                    <ColumnDefinition Width="Auto" 296.                                      MinWidth="100" /> 297.                    <ColumnDefinition Width="Auto" 298.                                      MinWidth="200" /> 299.                </Grid.ColumnDefinitions> 300.                <!-- Subject --> 301.                <TextBlock x:Name="SubjectLabel" 302.                           Grid.Row="0" 303.                           Grid.Column="0" 304.                           Margin="0,15,0,2" 305.                           telerik:LocalizationManager.ResourceKey="Subject" 306.                           Style="{StaticResource FormElementTextBlockStyle}" /> 307.                <TextBox x:Name="Subject" 308.                         Grid.Row="0" 309.                         Grid.Column="1" 310.                         MinHeight="22" 311.                         Padding="4 2" 312.                         Width="340" 313.                         HorizontalAlignment="Left" 314.                         Text="{Binding Subject, Mode=TwoWay}" 315.                         MaxLength="255" 316.                         telerik:StyleManager.Theme="{StaticResource Theme}" 317.                         Margin="10,12,20,2" /> 318.                <!-- Description --> 319.                <TextBlock x:Name="DescriptionLabel" 320.                           Grid.Row="1" 321.                           Grid.Column="0" 322.                           Margin="0,13,0,2" 323.                           telerik:LocalizationManager.ResourceKey="Body" 324.                           Style="{StaticResource FormElementTextBlockStyle}" /> 325.                <TextBox x:Name="Body" 326.                         VerticalAlignment="top" 327.                         Grid.Row="1" 328.                         Grid.Column="1" 329.                         Height="Auto" 330.                         MaxHeight="82" 331.                         Width="340" 332.                         HorizontalAlignment="Left" 333.                         MinHeight="22" 334.                         Padding="4 2" 335.                         TextWrapping="Wrap" 336.                         telerik:StyleManager.Theme="{StaticResource Theme}" 337.                         Text="{Binding Body, Mode=TwoWay}" 338.                         AcceptsReturn="true" 339.                         Margin="10,10,20,2" 340.                         HorizontalScrollBarVisibility="Auto" 341.                         VerticalScrollBarVisibility="Auto" /> 342.                <!-- Start/End date --> 343.                <TextBlock x:Name="StartDateLabel" 344.                           Grid.Row="2" 345.                           Grid.Column="0" 346.                           Margin="0,13,0,2" 347.                           telerik:LocalizationManager.ResourceKey="StartTime" 348.                           Style="{StaticResource FormElementTextBlockStyle}" /> 349.                <telerikScheduler:DateTimePicker x:Name="StartDateTime" 350.                                                 Height="22" 351.                                                 Grid.Row="2" 352.                                                 Grid.Column="1" 353.                                                 HorizontalAlignment="Left" 354.                                                 Margin="10,10,20,2" 355.                                                 Style="{StaticResource FormElementStyle}" 356.                                                 SelectedDateTime="{Binding Start, Mode=TwoWay}" 357.                                                 telerikScheduler:StartEndDatePicker.EndPicker="{Binding ElementName=EndDateTime}" 358.                                                 IsTabStop="False" 359.                                                 IsEnabled="False" /> 360.                <TextBlock x:Name="EndDateLabel" 361.                           Grid.Row="3" 362.                           Grid.Column="0" 363.                           Margin="0,13,0,2" 364.                           telerik:LocalizationManager.ResourceKey="EndTime" 365.                           Style="{StaticResource FormElementTextBlockStyle}" /> 366.                <telerikScheduler:DateTimePicker x:Name="EndDateTime" 367.                                                 Height="22" 368.                                                 Grid.Row="3" 369.                                                 Grid.Column="1" 370.                                                 HorizontalAlignment="Left" 371.                                                 Margin="10,10,20,2" 372.                                                 Style="{StaticResource FormElementStyle}" 373.                                                 IsTabStop="False" 374.                                                 IsEnabled="False" 375.                                                 SelectedDateTime="{Binding End, Mode=TwoWay}" /> 376.                <!-- Is-all-day selector --> 377.                <CheckBox x:Name="AllDayEventCheckbox" 378.                          IsChecked="{Binding IsAllDayEvent, Mode=TwoWay}" 379.                          Grid.Row="4" 380.                          Grid.Column="1" 381.                          Margin="10,10,20,2" 382.                          HorizontalAlignment="Left" 383.                          telerik:StyleManager.Theme="{StaticResource Theme}" 384.                          telerik:LocalizationManager.ResourceKey="AllDayEvent"> 385.                    <telerik:CommandManager.InputBindings> 386.                        <telerik:InputBindingCollection> 387.                            <telerik:MouseBinding Command="telerikScheduler:RadSchedulerCommands.ChangeTimePickersVisibility" 388.                                                  Gesture="LeftClick" /> 389.                        </telerik:InputBindingCollection> 390.                    </telerik:CommandManager.InputBindings> 391.                </CheckBox> 392.                <Grid Grid.Row="5" 393.                      Grid.ColumnSpan="2"> 394.                    <Grid.ColumnDefinitions> 395.                        <ColumnDefinition Width="Auto" 396.                                          MinWidth="100" /> 397.                        <ColumnDefinition Width="Auto" 398.                                          MinWidth="200" /> 399.                    </Grid.ColumnDefinitions> 400.                    <Grid.RowDefinitions> 401.                        <RowDefinition Height="Auto" /> 402.                        <RowDefinition Height="Auto" /> 403.                    </Grid.RowDefinitions> 404.                    <TextBlock Text="Applicant" 405.                               Margin="0,13,0,2" 406.                               Style="{StaticResource FormElementTextBlockStyle}" /> 407.                    <telerikInput:RadComboBox IsEditable="False" 408.                                              Grid.Column="1" 409.                                              Height="24" 410.                                              VerticalAlignment="Center" 411.                                              ItemsSource="{Binding Source={StaticResource DataContextProxy}, Path=DataSource.ApplicantList}" 412.                                              SelectedValue="{Binding ApplicantID, Mode=TwoWay}" 413.                                              SelectedValuePath="ApplicantID" 414.                                              DisplayMemberPath="FirstName" /> 415.                       416.                    <TextBlock Text="Job" 417.                               Margin="0,13,0,2" 418.                               Grid.Row="1" 419.                               Style="{StaticResource FormElementTextBlockStyle}" /> 420.                    <telerikInput:RadComboBox IsEditable="False" 421.                                              Grid.Column="1" 422.                                              Grid.Row="1" 423.                                              Height="24" 424.                                              VerticalAlignment="Center" 425.                                              ItemsSource="{Binding Source={StaticResource DataContextProxy}, Path=DataSource.JobsList}" 426.                                              SelectedValue="{Binding PostingID, Mode=TwoWay}" 427.                                              SelectedValuePath="PostingID" 428.                                              DisplayMemberPath="JobTitle"/> 429.                </Grid> 430.                    <!-- Resources --> 431.                <Grid x:Name="ResourcesLayout" 432.                      Grid.Row="7" 433.                      Grid.Column="0" 434.                      Grid.ColumnSpan="2" 435.                      MaxHeight="130" 436.                      Margin="20,5,20,0"> 437.                    <Border Margin="0" 438.                            BorderThickness="1" 439.                            BorderBrush="{StaticResource GenericShallowBorderBrush}" 440.                            Visibility="{Binding ElementName=ResourcesScrollViewer, Path=ComputedVerticalScrollBarVisibility}"></Border> 441.                    <ScrollViewer x:Name="ResourcesScrollViewer" 442.                                  IsTabStop="false" 443.                                  Grid.Row="6" 444.                                  Grid.Column="0" 445.                                  Grid.ColumnSpan="2" 446.                                  Margin="1" 447.                                  telerik:StyleManager.Theme="{StaticResource Theme}" 448.                                  VerticalScrollBarVisibility="Auto"> 449.                        <scheduler:ResourcesItemsControl x:Name="PART_Resources" 450.                                                         HorizontalAlignment="Left" 451.                                                         Padding="0,2,0,5" 452.                                                         IsTabStop="false" 453.                                                         ItemsSource="{TemplateBinding ResourceTypeModels}" 454.                                                         ItemTemplateSelector="{StaticResource ItemTemplateSelector}" /> 455.                    </ScrollViewer> 456.                </Grid> 457.                <StackPanel x:Name="FooterControls" 458.                            Margin="5 10 10 10" 459.                            Grid.Row="8" 460.                            Grid.Column="1" 461.                            HorizontalAlignment="Left" 462.                            Orientation="Horizontal"> 463.                    <telerik:RadButton x:Name="OKButton" 464.                                       Margin="5" 465.                                       Padding="10 0" 466.                                       MinWidth="80" 467.                                       Command="telerikScheduler:RadSchedulerCommands.SaveAppointment" 468.                                       telerik:StyleManager.Theme="{StaticResource Theme}" 469.                                       telerikNavigation:RadWindow.ResponseButton="Accept" 470.                                       telerik:LocalizationManager.ResourceKey="SaveAndCloseCommandText"> 471.                    </telerik:RadButton> 472.                    <telerik:RadButton x:Name="CancelButton" 473.                                       Margin="5" 474.                                       Padding="10 0" 475.                                       MinWidth="80" 476.                                       telerik:LocalizationManager.ResourceKey="Cancel" 477.                                       telerik:StyleManager.Theme="{StaticResource Theme}" 478.                                       telerikNavigation:RadWindow.ResponseButton="Cancel" 479.                                       Command="telerik:WindowCommands.Close"> 480.                    </telerik:RadButton> 481.                </StackPanel> 482.            </Grid> 483.            <vsm:VisualStateManager.VisualStateGroups> 484.                <vsm:VisualStateGroup x:Name="RecurrenceRuleState"> 485.                    <vsm:VisualState x:Name="RecurrenceRuleIsNull"> 486.                        <Storyboard> 487.                            <ObjectAnimationUsingKeyFrames Storyboard.TargetName="StartDateTime" 488.                                                           Storyboard.TargetProperty="IsEnabled" 489.                                                           Duration="0"> 490.                                <DiscreteObjectKeyFrame KeyTime="0" 491.                                                        Value="True" /> 492.                            </ObjectAnimationUsingKeyFrames> 493.                            <ObjectAnimationUsingKeyFrames Storyboard.TargetName="EndDateTime" 494.                                                           Storyboard.TargetProperty="IsEnabled" 495.                                                           Duration="0"> 496.                                <DiscreteObjectKeyFrame KeyTime="0" 497.                                                        Value="True" /> 498.                            </ObjectAnimationUsingKeyFrames> 499.                            <ObjectAnimationUsingKeyFrames Storyboard.TargetName="AllDayEventCheckbox" 500.                                                           Storyboard.TargetProperty="IsEnabled" 501.                                                           Duration="0"> 502.                                <DiscreteObjectKeyFrame KeyTime="0" 503.                                                        Value="True" /> 504.                            </ObjectAnimationUsingKeyFrames> 505.                        </Storyboard> 506.                    </vsm:VisualState> 507.                    <vsm:VisualState x:Name="RecurrenceRuleIsNotNull"> 508.                        <Storyboard> 509.                            <ObjectAnimationUsingKeyFrames Storyboard.TargetName="StartDateTime" 510.                                                           Storyboard.TargetProperty="IsEnabled" 511.                                                           Duration="0"> 512.                                <DiscreteObjectKeyFrame KeyTime="0" 513.                                                        Value="False" /> 514.                            </ObjectAnimationUsingKeyFrames> 515.                            <ObjectAnimationUsingKeyFrames Storyboard.TargetName="EndDateTime" 516.                                                           Storyboard.TargetProperty="IsEnabled" 517.                                                           Duration="0"> 518.                                <DiscreteObjectKeyFrame KeyTime="0" 519.                                                        Value="False" /> 520.                            </ObjectAnimationUsingKeyFrames> 521.                            <ObjectAnimationUsingKeyFrames Storyboard.TargetName="AllDayEventCheckbox" 522.                                                           Storyboard.TargetProperty="IsEnabled" 523.                                                           Duration="0"> 524.                                <DiscreteObjectKeyFrame KeyTime="0" 525.                                                        Value="False" /> 526.                            </ObjectAnimationUsingKeyFrames> 527.                        </Storyboard> 528.                    </vsm:VisualState> 529.                </vsm:VisualStateGroup> 530.            </vsm:VisualStateManager.VisualStateGroups> 531.        </StackPanel> 532.    </ControlTemplate> 533.    <DataTemplate x:Key="AppointmentDialogWindowHeaderDataTemplate"> 534.        <StackPanel Orientation="Horizontal" 535.                    MaxWidth="400"> 536.            <TextBlock telerik:LocalizationManager.ResourceKey="Event" 537.                       Visibility="{Binding Appointment.IsAllDayEvent, Converter={StaticResource BooleanToVisibilityConverter}}" /> 538.            <TextBlock telerik:LocalizationManager.ResourceKey="Appointment" 539.                       Visibility="{Binding Appointment.IsAllDayEvent, Converter={StaticResource InvertedBooleanToVisibilityConverter}}" /> 540.            <TextBlock Text=" - " /> 541.            <TextBlock x:Name="SubjectTextBlock" 542.                       Visibility="{Binding Appointment.Subject, Converter={StaticResource NullToVisibilityConverter}}" 543.                       Text="{Binding Appointment.Subject}" /> 544.            <TextBlock telerik:LocalizationManager.ResourceKey="Untitled" 545.                       Visibility="{Binding Appointment.Subject, Converter={StaticResource InvertedNullToVisibilityConverter}}" /> 546.        </StackPanel> 547.    </DataTemplate> 548.    <Style x:Key="EditAppointmentStyle" 549.           TargetType="telerikScheduler:AppointmentDialogWindow"> 550.        <Setter Property="IconTemplate" 551.                Value="{StaticResource IconDataEditTemplate}" /> 552.        <Setter Property="HeaderTemplate" 553.                Value="{StaticResource AppointmentDialogWindowHeaderDataTemplate}" /> 554.        <Setter Property="Background" 555.                Value="{StaticResource DialogWindowBackground}" /> 556.        <Setter Property="Template" 557.                Value="{StaticResource EditAppointmentTemplate}" /> 558.    </Style> 559.</UserControl.Resources> The first line there is the DataContextProxy I mentioned previously- we use that again to work a bit of magic in this template. Where we start getting into the dialog in question is line 132, but line 407 is where things start getting interesting.  The ItemsSource is pointing at a list that exists in my ViewModel (or code-behind, if it is used as a DataContext), the SelectedValue is the item I am actually binding from the applicant (note the TwoWay binding), and SelectedValuePath and DisplayMemberPath ensure the proper applicant is being displayed from the collection.  You will also see similar starting on line 420 where I do the same for the Jobs we'll be displaying. Just to wrap-up the Xaml, here's the RadScheduler declaraction that ties this all together and will be the main focus of our view: 01.<telerikScheduler:RadScheduler x:Name="xJobsScheduler" 02.                  Grid.Row="1" 03.                  Grid.Column="1" 04.                  Width="800" 05.                  MinWidth="600" 06.                  Height="500" 07.                  MinHeight="300" 08.                  AppointmentsSource="{Binding Interviews}" 09.                  EditAppointmentStyle="{StaticResource EditAppointmentStyle}" 10.                  command:AppointmentAddedEventClass.Command="{Binding AddAppointmentCommand}" 11.                  command:ApptCreatedEventClass.Command="{Binding ApptCreatingCommand}" 12.                  command:ApptEditedEventClass.Command="{Binding ApptEditedCommand}" 13.                  command:ApptDeletedEventClass.Command="{Binding ApptDeletedCommand}"> 14.</telerikScheduler:RadScheduler> Now, we get to the ViewModel and what it takes to get that rigged up.  And for those of you who remember the jobs post, those command:s in the Xaml are pointing to attached behavior commands that reproduce the respective events.  This becomes very handy when we're setting up the code-behind version. ;) ViewModel I've been liking this approach so far, so I'm going to put the entire ViewModel here and then go into the lines of interest.  Of course, feel free to ask me questions about anything that isn't clear (by line number, ideally) so I can help out if I have missed anything important: 001.public class SchedulerViewModel : ViewModelBase 002.{ 003.    private readonly IEventAggregator eventAggregator; 004.    private readonly IRegionManager regionManager; 005.   006.    public RecruitingContext context; 007.   008.    private ObservableItemCollection<InterviewAppointment> _interviews = new ObservableItemCollection<InterviewAppointment>(); 009.    public ObservableItemCollection<InterviewAppointment> Interviews 010.    { 011.        get { return _interviews; } 012.        set 013.        { 014.            if (_interviews != value) 015.            { 016.                _interviews = value; 017.                NotifyChanged("Interviews"); 018.            } 019.        } 020.    } 021.   022.    private QueryableCollectionView _jobsList; 023.    public QueryableCollectionView JobsList 024.    { 025.        get { return this._jobsList; } 026.        set 027.        { 028.            if (this._jobsList != value) 029.            { 030.                this._jobsList = value; 031.                this.NotifyChanged("JobsList"); 032.            } 033.        } 034.    } 035.   036.    private QueryableCollectionView _applicantList; 037.    public QueryableCollectionView ApplicantList 038.    { 039.        get { return _applicantList; } 040.        set 041.        { 042.            if (_applicantList != value) 043.            { 044.                _applicantList = value; 045.                NotifyChanged("ApplicantList"); 046.            } 047.        } 048.    } 049.   050.    public DelegateCommand<object> AddAppointmentCommand { get; set; } 051.    public DelegateCommand<object> ApptCreatingCommand { get; set; } 052.    public DelegateCommand<object> ApptEditedCommand { get; set; } 053.    public DelegateCommand<object> ApptDeletedCommand { get; set; } 054.   055.    public SchedulerViewModel(IEventAggregator eventAgg, IRegionManager regionmanager) 056.    { 057.        // set Unity items 058.        this.eventAggregator = eventAgg; 059.        this.regionManager = regionmanager; 060.   061.        // load our context 062.        context = new RecruitingContext(); 063.        LoadOperation<Interview> loadOp = context.Load(context.GetInterviewsQuery()); 064.        loadOp.Completed += new EventHandler(loadOp_Completed); 065.   066.        this._jobsList = new QueryableCollectionView(context.JobPostings); 067.        context.Load(context.GetJobPostingsQuery()); 068.   069.        this._applicantList = new QueryableCollectionView(context.Applicants); 070.        context.Load(context.GetApplicantsQuery()); 071.   072.        AddAppointmentCommand = new DelegateCommand<object>(this.AddAppt); 073.        ApptCreatingCommand = new DelegateCommand<object>(this.ApptCreating); 074.        ApptEditedCommand = new DelegateCommand<object>(this.ApptEdited); 075.        ApptDeletedCommand = new DelegateCommand<object>(this.ApptDeleted); 076.   077.    } 078.   079.    void loadOp_Completed(object sender, EventArgs e) 080.    { 081.        LoadOperation loadop = sender as LoadOperation; 082.   083.        foreach (var ent in loadop.Entities) 084.        { 085.            _interviews.Add(EntityToAppointment(ent as Interview)); 086.        } 087.    } 088.   089.    #region Appointment Adding 090.   091.    public void AddAppt(object obj) 092.    { 093.        // now we have a new InterviewAppointment to add to our QCV :) 094.        InterviewAppointment newInterview = obj as InterviewAppointment; 095.   096.        this.context.Interviews.Add(AppointmentToEntity(newInterview)); 097.        this.context.SubmitChanges((s) => 098.        { 099.            ActionHistory myAction = new ActionHistory(); 100.            myAction.InterviewID = newInterview.InterviewID; 101.            myAction.PostingID = newInterview.PostingID; 102.            myAction.ApplicantID = newInterview.ApplicantID; 103.            myAction.Description = String.Format("Interview with {0} has been created by {1}", newInterview.ApplicantID.ToString(), "default user"); 104.            myAction.TimeStamp = DateTime.Now; 105.            eventAggregator.GetEvent<AddActionEvent>().Publish(myAction); 106.        } 107.            , null); 108.    } 109.   110.    public void ApptCreating(object obj) 111.    { 112.        // handled in the behavior, just a placeholder to ensure it runs :) 113.    } 114.   115.    #endregion 116.   117.    #region Appointment Editing 118.   119.    public void ApptEdited(object obj) 120.    { 121.        Interview editedInterview = (from x in context.Interviews 122.                            where x.InterviewID == (obj as InterviewAppointment).InterviewID 123.                            select x).SingleOrDefault(); 124.   125.        CopyAppointmentEdit(editedInterview, obj as InterviewAppointment); 126.   127.        context.SubmitChanges((s) => { 128.            ActionHistory myAction = new ActionHistory(); 129.            myAction.InterviewID = editedInterview.InterviewID; 130.            myAction.PostingID = editedInterview.PostingID; 131.            myAction.ApplicantID = editedInterview.ApplicantID; 132.            myAction.Description = String.Format("Interview with {0} has been modified by {1}", editedInterview.ApplicantID.ToString(), "default user"); 133.            myAction.TimeStamp = DateTime.Now; 134.            eventAggregator.GetEvent<AddActionEvent>().Publish(myAction); } 135.            , null); 136.    } 137.   138.    #endregion 139.   140.    #region Appointment Deleting 141.   142.    public void ApptDeleted(object obj) 143.    { 144.        Interview deletedInterview = (from x in context.Interviews 145.                                      where x.InterviewID == (obj as InterviewAppointment).InterviewID 146.                                      select x).SingleOrDefault(); 147.   148.        context.Interviews.Remove(deletedInterview); 149.        context.SubmitChanges((s) => 150.        { 151.            ActionHistory myAction = new ActionHistory(); 152.            myAction.InterviewID = deletedInterview.InterviewID; 153.            myAction.PostingID = deletedInterview.PostingID; 154.            myAction.ApplicantID = deletedInterview.ApplicantID; 155.            myAction.Description = String.Format("Interview with {0} has been deleted by {1}", deletedInterview.ApplicantID.ToString(), "default user"); 156.            myAction.TimeStamp = DateTime.Now; 157.            eventAggregator.GetEvent<AddActionEvent>().Publish(myAction); 158.        } 159.            , null); 160.    } 161.   162.    #endregion 163.   164.    #region Appointment Helpers :) 165.   166.    public Interview AppointmentToEntity(InterviewAppointment ia) 167.    { 168.        Interview newInterview = new Interview(); 169.        newInterview.Subject = ia.Subject; 170.        newInterview.Body = ia.Body; 171.        newInterview.Start = ia.Start; 172.        newInterview.End = ia.End; 173.        newInterview.ApplicantID = ia.ApplicantID; 174.        newInterview.PostingID = ia.PostingID; 175.        newInterview.InterviewID = ia.InterviewID; 176.   177.        return newInterview; 178.    } 179.   180.    public InterviewAppointment EntityToAppointment(Interview ia) 181.    { 182.        InterviewAppointment newInterview = new InterviewAppointment(); 183.        newInterview.Subject = ia.Subject; 184.        newInterview.Body = ia.Body; 185.        newInterview.Start = ia.Start; 186.        newInterview.End = ia.End; 187.        newInterview.ApplicantID = ia.ApplicantID; 188.        newInterview.PostingID = ia.PostingID; 189.        newInterview.InterviewID = ia.InterviewID; 190.   191.        return newInterview; 192.    } 193.   194.    public void CopyAppointmentEdit(Interview entityInterview, InterviewAppointment appointmentInterview) 195.    { 196.        entityInterview.Subject = appointmentInterview.Subject; 197.        entityInterview.Body = appointmentInterview.Body; 198.        entityInterview.Start = appointmentInterview.Start; 199.        entityInterview.End = appointmentInterview.End; 200.        entityInterview.ApplicantID = appointmentInterview.ApplicantID; 201.        entityInterview.PostingID = appointmentInterview.PostingID; 202.    } 203.   204.    #endregion 205.} One thing we're doing here which you won't see in any of the other ViewModels is creating a duplicate collection.  I know this is something which will be fixed down the line for using RadScheduler, simplifying this process, but with WCF RIA changing as it does I wanted to ensure functionality would remain consistent as I continued development on this application.  So, I do a little bit of duplication, but for the greater good.  This all takes place starting on line 79, so for every entity that comes back we add it to the collection that is bound to RadScheduler.  Otherwise, the DelegateCommands that you see correspond directly to the events they are named after.  In each case, rather than sending over the full event arguments, I just send in the appointment in question (coming through as the object obj in all cases) so I can add (line 91), edit (line 119), and delete appointments (line 142) like normal.  This just ensures they get updated back to my database.  Also, the one bit of code you won't see is for the Appointment Creating (line 110) event- that is because in the command I've created I simply make the replacement I need to: 1.void element_AppointmentCreating(object sender, AppointmentCreatingEventArgs e) 2.{ 3.    e.NewAppointment = new InterviewAppointment(); 4.    base.ExecuteCommand(); 5.} And the ViewModel is none the wiser, the appointments just work as far as it is concerned since as they are created they become InterviewAppointments.  End result?  I've customized my EditAppointmentDialog as follows: And adding, editing, and deleting appointments works like a charm.  I can even 'edit' by moving appointments around RadScheduler, so as they are dropped into a timeslot they perform their full edit routine and things get updated. And then, the Code-Behind Version Perhaps the thing I like the most about doing one then the other is I get to steal 90% or more of the code from the MVVM version.  For example, the only real changes to the Code-Behind Xaml file exist in the control declaration, in which I use events instead of attached-behavior-event-commands: 01.<telerikScheduler:RadScheduler x:Name="xJobsScheduler" 02.                  Grid.Row="1" 03.                  Grid.Column="1" 04.                  Width="800" 05.                  MinWidth="600" 06.                  Height="500" 07.                  MinHeight="300" 08.                  EditAppointmentStyle="{StaticResource EditAppointmentStyle}" 09.                  AppointmentAdded="xJobsScheduler_AppointmentAdded" 10.                  AppointmentCreating="xJobsScheduler_AppointmentCreating" 11.                  AppointmentEdited="xJobsScheduler_AppointmentEdited" 12.                  AppointmentDeleted="xJobsScheduler_AppointmentDeleted"> 13.</telerikScheduler:RadScheduler> Easy, right?  Otherwise, all the same styling in UserControl.Resources was re-used, right down to the DataContextProxy that lets us bind to a collection from our viewmodel (in this case, our code-behind) to use within the DataTemplate.  The code conversion gets even easier, as I could literally copy and paste almost everything from the ViewModel to my Code-Behind, just a matter of pasting the right section into the right event.  Here's the code-behind as proof: 001.public partial class SchedulingView : UserControl, INotifyPropertyChanged 002.{ 003.    public RecruitingContext context; 004.   005.    private QueryableCollectionView _jobsList; 006.    public QueryableCollectionView JobsList 007.    { 008.        get { return this._jobsList; } 009.        set 010.        { 011.            if (this._jobsList != value) 012.            { 013.                this._jobsList = value; 014.                this.NotifyChanged("JobsList"); 015.            } 016.        } 017.    } 018.   019.    private QueryableCollectionView _applicantList; 020.    public QueryableCollectionView ApplicantList 021.    { 022.        get { return _applicantList; } 023.        set 024.        { 025.            if (_applicantList != value) 026.            { 027.                _applicantList = value; 028.                NotifyChanged("ApplicantList"); 029.            } 030.        } 031.    } 032.   033.    private ObservableItemCollection<InterviewAppointment> _interviews = new ObservableItemCollection<InterviewAppointment>(); 034.    public ObservableItemCollection<InterviewAppointment> Interviews 035.    { 036.        get { return _interviews; } 037.        set 038.        { 039.            if (_interviews != value) 040.            { 041.                _interviews = value; 042.                NotifyChanged("Interviews"); 043.            } 044.        } 045.    } 046.   047.    public SchedulingView() 048.    { 049.        InitializeComponent(); 050.   051.        this.DataContext = this; 052.   053.        this.Loaded += new RoutedEventHandler(SchedulingView_Loaded); 054.    } 055.   056.    void SchedulingView_Loaded(object sender, RoutedEventArgs e) 057.    { 058.        this.xJobsScheduler.AppointmentsSource = Interviews; 059.   060.        context = new RecruitingContext(); 061.           062.        LoadOperation loadop = context.Load(context.GetInterviewsQuery()); 063.        loadop.Completed += new EventHandler(loadop_Completed); 064.   065.        this._applicantList = new QueryableCollectionView(context.Applicants); 066.        context.Load(context.GetApplicantsQuery()); 067.   068.        this._jobsList = new QueryableCollectionView(context.JobPostings); 069.        context.Load(context.GetJobPostingsQuery()); 070.    } 071.   072.    void loadop_Completed(object sender, EventArgs e) 073.    { 074.        LoadOperation loadop = sender as LoadOperation; 075.   076.        _interviews.Clear(); 077.   078.        foreach (var ent in loadop.Entities) 079.        { 080.            _interviews.Add(EntityToAppointment(ent as Interview)); 081.        } 082.    } 083.   084.    private void xJobsScheduler_AppointmentAdded(object sender, Telerik.Windows.Controls.AppointmentAddedEventArgs e) 085.    { 086.        // now we have a new InterviewAppointment to add to our QCV :) 087.        InterviewAppointment newInterview = e.Appointment as InterviewAppointment; 088.   089.        this.context.Interviews.Add(AppointmentToEntity(newInterview)); 090.        this.context.SubmitChanges((s) => 091.        { 092.            ActionHistory myAction = new ActionHistory(); 093.            myAction.InterviewID = newInterview.InterviewID; 094.            myAction.PostingID = newInterview.PostingID; 095.            myAction.ApplicantID = newInterview.ApplicantID; 096.            myAction.Description = String.Format("Interview with {0} has been created by {1}", newInterview.ApplicantID.ToString(), "default user"); 097.            myAction.TimeStamp = DateTime.Now; 098.            context.ActionHistories.Add(myAction); 099.            context.SubmitChanges(); 100.        } 101.            , null); 102.    } 103.   104.    private void xJobsScheduler_AppointmentCreating(object sender, Telerik.Windows.Controls.AppointmentCreatingEventArgs e) 105.    { 106.        e.NewAppointment = new InterviewAppointment(); 107.    } 108.   109.    private void xJobsScheduler_AppointmentEdited(object sender, Telerik.Windows.Controls.AppointmentEditedEventArgs e) 110.    { 111.        Interview editedInterview = (from x in context.Interviews 112.                                     where x.InterviewID == (e.Appointment as InterviewAppointment).InterviewID 113.                                     select x).SingleOrDefault(); 114.   115.        CopyAppointmentEdit(editedInterview, e.Appointment as InterviewAppointment); 116.   117.        context.SubmitChanges((s) => 118.        { 119.            ActionHistory myAction = new ActionHistory(); 120.            myAction.InterviewID = editedInterview.InterviewID; 121.            myAction.PostingID = editedInterview.PostingID; 122.            myAction.ApplicantID = editedInterview.ApplicantID; 123.            myAction.Description = String.Format("Interview with {0} has been modified by {1}", editedInterview.ApplicantID.ToString(), "default user"); 124.            myAction.TimeStamp = DateTime.Now; 125.            context.ActionHistories.Add(myAction); 126.            context.SubmitChanges(); 127.        } 128.            , null); 129.    } 130.   131.    private void xJobsScheduler_AppointmentDeleted(object sender, Telerik.Windows.Controls.AppointmentDeletedEventArgs e) 132.    { 133.        Interview deletedInterview = (from x in context.Interviews 134.                                      where x.InterviewID == (e.Appointment as InterviewAppointment).InterviewID 135.                                      select x).SingleOrDefault(); 136.   137.        context.Interviews.Remove(deletedInterview); 138.        context.SubmitChanges((s) => 139.        { 140.            ActionHistory myAction = new ActionHistory(); 141.            myAction.InterviewID = deletedInterview.InterviewID; 142.            myAction.PostingID = deletedInterview.PostingID; 143.            myAction.ApplicantID = deletedInterview.ApplicantID; 144.            myAction.Description = String.Format("Interview with {0} has been deleted by {1}", deletedInterview.ApplicantID.ToString(), "default user"); 145.            myAction.TimeStamp = DateTime.Now; 146.            context.ActionHistories.Add(myAction); 147.            context.SubmitChanges(); 148.        } 149.            , null); 150.    } 151.   152.    #region Appointment Helpers :) 153.   154.    public Interview AppointmentToEntity(InterviewAppointment ia) 155.    { 156.        Interview newInterview = new Interview(); 157.        newInterview.Subject = ia.Subject; 158.        newInterview.Body = ia.Body; 159.        newInterview.Start = ia.Start; 160.        newInterview.End = ia.End; 161.        newInterview.ApplicantID = ia.ApplicantID; 162.        newInterview.PostingID = ia.PostingID; 163.        newInterview.InterviewID = ia.InterviewID; 164.   165.        return newInterview; 166.    } 167.   168.    public InterviewAppointment EntityToAppointment(Interview ia) 169.    { 170.        InterviewAppointment newInterview = new InterviewAppointment(); 171.        newInterview.Subject = ia.Subject; 172.        newInterview.Body = ia.Body; 173.        newInterview.Start = ia.Start; 174.        newInterview.End = ia.End; 175.        newInterview.ApplicantID = ia.ApplicantID; 176.        newInterview.PostingID = ia.PostingID; 177.        newInterview.InterviewID = ia.InterviewID; 178.   179.        return newInterview; 180.    } 181.   182.    public void CopyAppointmentEdit(Interview entityInterview, InterviewAppointment appointmentInterview) 183.    { 184.        entityInterview.Subject = appointmentInterview.Subject; 185.        entityInterview.Body = appointmentInterview.Body; 186.        entityInterview.Start = appointmentInterview.Start; 187.        entityInterview.End = appointmentInterview.End; 188.        entityInterview.ApplicantID = appointmentInterview.ApplicantID; 189.        entityInterview.PostingID = appointmentInterview.PostingID; 190.    } 191.   192.    #endregion 193.   194.    #region INotifyPropertyChanged Members 195.   196.    public event PropertyChangedEventHandler PropertyChanged; 197.   198.    public void NotifyChanged(string propertyName) 199.    { 200.        if (string.IsNullOrEmpty(propertyName)) 201.            throw new ArgumentException("propertyName"); 202.   203.        PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); 204.    } 205.   206.    #endregion 207.} Nice... right? :) One really important thing to note as well.  See on line 51 where I set the DataContext before the Loaded event?  This is super important, as if you don't have this set before the usercontrol is loaded, the DataContextProxy has no context to use and your EditAppointmentDialog Job/Applicant dropdowns will be blank and empty.  Trust me on this, took a little bit of debugging to figure out that by setting the DataContext post-loaded would only lead to disaster and frustration.  Otherwise, the only other real difference is that instead of sending an ActionHistory item through an event to get added to the database and saved, I do those right in the callback from submitting.  The Result Again, I only have to post one picture because these bad boys used nearly identical code for both the MVVM and the code-behind views, so our end result is... So what have we learned here today?  One, for the most part this MVVM thing is somewhat easy.  Yeah, you sometimes have to write a bunch of extra code, but with the help of a few useful snippits you can turn the process into a pretty streamlined little workflow.  Heck, this story gets even easier as you can see in this blog post by Michael Washington- specifically run a find on 'InvokeCommandAction' and you'll see the section regarding the command on TreeView in Blend 4.  Brilliant!  MVVM never looked so sweet! Otherwise, it is business as usual with RadScheduler for Silverlight whichever path you're choosing for your development.  Between now and the next post, I'll be cleaning up styles a bit (those RadComboBoxes are a little too close to my labels!) and adding some to the RowDetailsViews for Applicants and Jobs, so you can see all the info for an appointment in the dropdown tab view.  Otherwise, we're about ready to call a wrap on this oneDid you know that DotNetSlackers also publishes .net articles written by top known .net Authors? We already have over 80 articles in several categories including Silverlight. Take a look: here.

    Read the article

  • Silverlight Recruiting Application Part 6 - Adding an Interview Scheduling Module/View

    Between the last post and this one I went ahead and carried the ideas for the Jobs module and view into the Applicants module and view- they're both doing more or less the same thing, except with different objects being at their core.  Made for an easy cut-and-paste operation with a few items being switched from one to another.  Now that we have the ability to add postings and applicants, wouldn't it be nice if we could schedule an interview?  Of course it would! Scheduling Module I think you get the drift from previous posts that these project structures start looking somewhat similar.  The interview scheduling module is no different than the rest- it gets a SchedulingModule.cs file at the root that inherits from IModule, and there is a single SchedulerView.xsml and SchedulerViewModel.cs setup for our V+VM.  We have one unique concern as we enter into this- RadScheduler deals with AppointmentsSource, not ItemsSource, so there are some special considerations to take into account when planning this module. First, I need something which inherits from AppointmentBase.  This is the core of the RadScheduler appointment, and if you are planning to do any form of custom appointment, you'll want it to inherit from this.  Then you can add-on functionality as needed.  Here is my addition to the mix, the InterviewAppointment: 01.public class InterviewAppointment : AppointmentBase 02.{ 03.    private int _applicantID; 04.    public int ApplicantID 05.    { 06.        get { return this._applicantID; } 07.        set 08.        { 09.            if (_applicantID != value) 10.            { 11.                _applicantID = value; 12.                OnPropertyChanged("ApplicantID"); 13.            } 14.        } 15.    } 16.   17.    private int _postingID; 18.    public int PostingID 19.    { 20.        get { return _postingID; } 21.        set 22.        { 23.            if (_postingID != value) 24.            { 25.                _postingID = value; 26.                OnPropertyChanged("PostingID"); 27.            } 28.        } 29.    } 30.   31.    private string _body; 32.    public string Body 33.    { 34.        get { return _body; } 35.        set 36.        { 37.            if (_body != value) 38.            { 39.                _body = value; 40.                OnPropertyChanged("Body"); 41.            } 42.        } 43.    } 44.   45.    private int _interviewID; 46.    public int InterviewID 47.    { 48.        get { return _interviewID; } 49.        set 50.        { 51.            if (_interviewID != value) 52.            { 53.                _interviewID = value; 54.                OnPropertyChanged("InterviewID"); 55.            } 56.        } 57.    } 58.   59.    public override IAppointment Copy() 60.    { 61.        IAppointment appointment = new InterviewAppointment(); 62.        appointment.CopyFrom(this);             63.        return appointment; 64.    } 65.   66.    public override void CopyFrom(IAppointment other) 67.    {             68.        base.CopyFrom(other); 69.        var appointment = other as InterviewAppointment; 70.        if (appointment != null) 71.        { 72.            ApplicantID = appointment.ApplicantID; 73.            PostingID = appointment.PostingID; 74.            Body = appointment.Body; 75.            InterviewID = appointment.InterviewID; 76.        } 77.    } 78.} Nothing too exciting going on here, we just make sure that our custom fields are persisted (specifically set in CopyFrom at the bottom) and notifications are fired- otherwise this ends up exactly like the standard appointment as far as interactions, etc.  But if we've got custom appointment items... that also means we need to customize what our appointment dialog window will look like. Customizing the Edit Appointment Dialog This initially sounds a lot more intimidating than it really is.  The first step here depends on what you're dealing with for theming, but for ease of everything I went ahead and extracted my templates in Blend for RadScheduler so I could modify it as I pleased.  For the faint of heart, the RadScheduler template is a few thousand lines of goodness since there are some very complex things going on in that control.  I've gone ahead and trimmed down the template parts I don't need as much as possible, so what is left is all that is relevant to the Edit Appointment Dialog.  Here's the resulting Xaml, with line numbers, so I can explain further: 001.<UserControl.Resources> 002.    <!-- begin Necessary Windows 7 Theme Resources for EditAppointmentTemplate --> 003.    <helpers:DataContextProxy x:Key="DataContextProxy" /> 004.       005.    <telerik:Windows7Theme x:Key="Theme" /> 006.    <SolidColorBrush x:Key="DialogWindowBackground" 007.                     Color="White" /> 008.    <SolidColorBrush x:Key="CategorySelectorBorderBrush" 009.                     Color="#FFB1B1B1" /> 010.    <LinearGradientBrush x:Key="RadToolBar_InnerBackground" 011.                         EndPoint="0.5,1" 012.                         StartPoint="0.5,0"> 013.        <GradientStop Color="#FFFDFEFF" 014.                      Offset="0" /> 015.        <GradientStop Color="#FFDDE9F7" 016.                      Offset="1" /> 017.        <GradientStop Color="#FFE6F0FA" 018.                      Offset="0.5" /> 019.        <GradientStop Color="#FFDCE6F4" 020.                      Offset="0.5" /> 021.    </LinearGradientBrush> 022.    <Style x:Key="FormElementTextBlockStyle" 023.           TargetType="TextBlock"> 024.        <Setter Property="HorizontalAlignment" 025.                Value="Right" /> 026.        <Setter Property="VerticalAlignment" 027.                Value="Top" /> 028.        <Setter Property="Margin" 029.                Value="15, 15, 0, 2" /> 030.    </Style> 031.    <Style x:Key="FormElementStyle" 032.           TargetType="FrameworkElement"> 033.        <Setter Property="Margin" 034.                Value="10, 10, 0, 2" /> 035.    </Style> 036.    <SolidColorBrush x:Key="GenericShallowBorderBrush" 037.                     Color="#FF979994" /> 038.    <telerik:BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" /> 039.    <telerikScheduler:ImportanceToBooleanConverter x:Key="ImportanceToBooleanConverter" /> 040.    <telerikScheduler:NullToVisibilityConverter x:Key="NullToVisibilityConverter" /> 041.    <telerikScheduler:InvertedNullToVisibilityConverter x:Key="InvertedNullToVisibilityConverter" /> 042.    <scheduler:ResourcesSeparatorConverter x:Key="ResourcesSeparatorConverter" /> 043.    <DataTemplate x:Key="IconDataEditTemplate"> 044.        <Image Source="/Telerik.Windows.Controls.Scheduler;component/Themes/Office/Images/cal.png" 045.               Margin="3,3,0,0" 046.               Width="16" 047.               Height="16" /> 048.    </DataTemplate> 049.    <DataTemplate x:Key="SingleSelectionTemplate"> 050.        <Grid VerticalAlignment="Stretch" 051.              HorizontalAlignment="Stretch"> 052.            <Grid.RowDefinitions> 053.                <RowDefinition Height="Auto" /> 054.            </Grid.RowDefinitions> 055.            <Grid.ColumnDefinitions> 056.                <ColumnDefinition Width="Auto" 057.                                  MinWidth="84" /> 058.                <ColumnDefinition Width="Auto" 059.                                  MinWidth="200" /> 060.            </Grid.ColumnDefinitions> 061.            <TextBlock x:Name="SelectionNameLabel" 062.                       Margin="0,13,4,2" 063.                       Text="{Binding ResourceType.DisplayName}" 064.                       Style="{StaticResource FormElementTextBlockStyle}" 065.                       Grid.Column="0" /> 066.            <telerikInput:RadComboBox ItemsSource="{Binding ResourceItems}" 067.                                      Width="185" 068.                                      Margin="5,10,20,2" 069.                                      HorizontalAlignment="Left" 070.                                      Grid.Column="1" 071.                                      ClearSelectionButtonVisibility="Visible" 072.                                      ClearSelectionButtonContent="Clear All" 073.                                      DisplayMemberPath="Resource.DisplayName" 074.                                      telerik:StyleManager.Theme="{StaticResource Theme}" 075.                                      SelectedItem="{Binding SelectedItem, Mode=TwoWay}" /> 076.        </Grid> 077.    </DataTemplate> 078.    <DataTemplate x:Key="MultipleSelectionTemplate"> 079.        <Grid VerticalAlignment="Stretch" 080.              HorizontalAlignment="Stretch"> 081.            <Grid.RowDefinitions> 082.                <RowDefinition Height="Auto" /> 083.            </Grid.RowDefinitions> 084.            <Grid.ColumnDefinitions> 085.                <ColumnDefinition Width="Auto" 086.                                  MinWidth="84" /> 087.                <ColumnDefinition Width="Auto" 088.                                  MinWidth="200" /> 089.            </Grid.ColumnDefinitions> 090.            <TextBlock x:Name="SelectionNameLabel" 091.                       Grid.Column="0" 092.                       Text="{Binding ResourceType.DisplayName}" 093.                       Margin="0,13,4,2" 094.                       Style="{StaticResource FormElementTextBlockStyle}" /> 095.            <telerikInput:RadComboBox Grid.Column="1" 096.                                      Width="185" 097.                                      HorizontalAlignment="Left" 098.                                      Margin="5,10,20,2" 099.                                      ItemsSource="{Binding ResourceItems}" 100.                                      SelectedIndex="{Binding SelectedIndex, Mode=TwoWay}" 101.                                      ClearSelectionButtonVisibility="Visible" 102.                                      ClearSelectionButtonContent="Clear All" 103.                                      telerik:StyleManager.Theme="{StaticResource Theme}"> 104.                <telerikInput:RadComboBox.ItemTemplate> 105.                    <DataTemplate> 106.                        <Grid HorizontalAlignment="Stretch" 107.                              VerticalAlignment="Stretch"> 108.                            <CheckBox VerticalAlignment="Center" 109.                                      HorizontalContentAlignment="Stretch" 110.                                      VerticalContentAlignment="Center" 111.                                      IsChecked="{Binding IsChecked, Mode=TwoWay}" 112.                                      Content="{Binding Resource.DisplayName}"> 113.                                <CheckBox.ContentTemplate> 114.                                    <DataTemplate> 115.                                        <TextBlock HorizontalAlignment="Stretch" 116.                                                   VerticalAlignment="Stretch" 117.                                                   Text="{Binding Content, RelativeSource={RelativeSource TemplatedParent}}" /> 118.                                    </DataTemplate> 119.                                </CheckBox.ContentTemplate> 120.                            </CheckBox> 121.                        </Grid> 122.                    </DataTemplate> 123.                </telerikInput:RadComboBox.ItemTemplate> 124.            </telerikInput:RadComboBox> 125.        </Grid> 126.    </DataTemplate> 127.    <scheduler:ResourceTypeTemplateSelector x:Key="ItemTemplateSelector" 128.                                            MultipleSelectionTemplate="{StaticResource MultipleSelectionTemplate}" 129.                                            SingleSelectionTemplate="{StaticResource SingleSelectionTemplate}" /> 130.    <!-- end Necessary Windows 7 Theme Resources for EditAppointmentTemplate -->  131.       132.    <ControlTemplate x:Key="EditAppointmentTemplate" 133.                     TargetType="telerikScheduler:AppointmentDialogWindow"> 134.        <StackPanel Background="{TemplateBinding Background}" 135.                    UseLayoutRounding="True"> 136.            <StackPanel Grid.Row="0" 137.                        Orientation="Horizontal" 138.                        Background="{StaticResource RadToolBar_InnerBackground}" 139.                        Grid.ColumnSpan="2" 140.                        Height="0"> 141.                <!-- Recurrence buttons --> 142.                <Border Margin="1,1,0,0" 143.                        Background="#50000000" 144.                        HorizontalAlignment="Left" 145.                        VerticalAlignment="Center" 146.                        Width="2" 147.                        Height="16"> 148.                    <Border Margin="0,0,1,1" 149.                            Background="#80FFFFFF" 150.                            HorizontalAlignment="Left" 151.                            Width="1" /> 152.                </Border> 153.                <Border Margin="1,1,0,0" 154.                        Background="#50000000" 155.                        HorizontalAlignment="Left" 156.                        VerticalAlignment="Center" 157.                        Width="2" 158.                        Height="16"> 159.                    <Border Margin="0,0,1,1" 160.                            Background="#80FFFFFF" 161.                            HorizontalAlignment="Left" 162.                            Width="1" /> 163.                </Border> 164.                <TextBlock telerik:LocalizationManager.ResourceKey="ShowAs" 165.                           VerticalAlignment="Center" 166.                           Margin="5,0,0,0" /> 167.                <telerikInput:RadComboBox ItemsSource="{TemplateBinding TimeMarkers}" 168.                                          Width="100" 169.                                          Height="20" 170.                                          VerticalAlignment="Center" 171.                                          Margin="5,0,0,0" 172.                                          ClearSelectionButtonVisibility="Visible" 173.                                          ClearSelectionButtonContent="Clear" 174.                                          SelectedItem="{Binding TimeMarker,RelativeSource={RelativeSource TemplatedParent},Mode=TwoWay}" 175.                                          telerik:StyleManager.Theme="{StaticResource Theme}"> 176.                    <telerikInput:RadComboBox.ItemTemplate> 177.                        <DataTemplate> 178.                            <StackPanel Orientation="Horizontal"> 179.                                <Rectangle Fill="{Binding TimeMarkerBrush}" 180.                                           Margin="2" 181.                                           Width="12" 182.                                           Height="12" /> 183.                                <TextBlock Text="{Binding TimeMarkerName}" 184.                                           Margin="2" /> 185.                            </StackPanel> 186.                        </DataTemplate> 187.                    </telerikInput:RadComboBox.ItemTemplate> 188.                </telerikInput:RadComboBox> 189.                <telerik:RadToggleButton x:Name="High" 190.                                         BorderThickness="0" 191.                                         Background="{StaticResource RadToolBar_InnerBackground}" 192.                                         DataContext="{TemplateBinding EditedAppointment}" 193.                                         telerik:StyleManager.Theme="{StaticResource Theme}" 194.                                         IsChecked="{Binding Importance,Mode=TwoWay, Converter={StaticResource ImportanceToBooleanConverter},ConverterParameter=High}" 195.                                         Margin="2,2,0,2" 196.                                         Width="23" 197.                                         Height="23" 198.                                         HorizontalContentAlignment="Center" 199.                                         ToolTipService.ToolTip="High importance" 200.                                         CommandParameter="High" 201.                                         Command="telerikScheduler:RadSchedulerCommands.SetAppointmentImportance"> 202.                    <StackPanel HorizontalAlignment="Center"> 203.                        <Path Stretch="Fill" 204.                              Height="10" 205.                              HorizontalAlignment="Center" 206.                              VerticalAlignment="Top" 207.                              Width="5.451" 208.                              Data="M200.39647,58.840393 C200.39337,58.336426 201.14566,57.683922 202.56244,57.684292 C204.06589,57.684685 204.73764,58.357765 204.72783,58.992363 C205.04649,61.795574 203.04713,64.181099 202.47388,66.133446 C201.93753,64.154961 199.9471,61.560352 200.39647,58.840393 z"> 209.                            <Path.Fill> 210.                                <LinearGradientBrush EndPoint="1.059,0.375" 211.                                                     StartPoint="-0.457,0.519"> 212.                                    <GradientStop Color="#FFFF0606" 213.                                                  Offset="0.609" /> 214.                                    <GradientStop Color="#FFBF0303" 215.                                                  Offset="0.927" /> 216.                                </LinearGradientBrush> 217.                            </Path.Fill> 218.                        </Path> 219.                        <Ellipse Height="3" 220.                                 HorizontalAlignment="Center" 221.                                 Margin="0,-1,0,0" 222.                                 VerticalAlignment="Top" 223.                                 Width="3"> 224.                            <Ellipse.Fill> 225.                                <RadialGradientBrush> 226.                                    <GradientStop Color="#FFFF0606" 227.                                                  Offset="0" /> 228.                                    <GradientStop Color="#FFBF0303" 229.                                                  Offset="1" /> 230.                                </RadialGradientBrush> 231.                            </Ellipse.Fill> 232.                        </Ellipse> 233.                    </StackPanel> 234.                </telerik:RadToggleButton> 235.                <telerik:RadToggleButton x:Name="Low" 236.                                         HorizontalContentAlignment="Center" 237.                                         BorderThickness="0" 238.                                         Background="{StaticResource RadToolBar_InnerBackground}" 239.                                         DataContext="{TemplateBinding EditedAppointment}" 240.                                         IsChecked="{Binding Importance,Mode=TwoWay, Converter={StaticResource ImportanceToBooleanConverter},ConverterParameter=Low}" 241.                                         Margin="0,2,0,2" 242.                                         Width="23" 243.                                         Height="23" 244.                                         ToolTipService.ToolTip="Low importance" 245.                                         CommandParameter="Low" 246.                                         telerik:StyleManager.Theme="{StaticResource Theme}" 247.                                         Command="telerikScheduler:RadSchedulerCommands.SetAppointmentImportance"> 248.                    <Path Stretch="Fill" 249.                          Height="12" 250.                          HorizontalAlignment="Center" 251.                          VerticalAlignment="Top" 252.                          Width="9" 253.                          Data="M222.40353,60.139881 L226.65768,60.139843 L226.63687,67.240196 L229.15347,67.240196 L224.37816,71.394943 L219.65274,67.240196 L222.37572,67.219345 z" 254.                          Stroke="#FF0365A7"> 255.                        <Path.Fill> 256.                            <LinearGradientBrush EndPoint="1.059,0.375" 257.                                                 StartPoint="-0.457,0.519"> 258.                                <GradientStop Color="#FFBBE4FF" /> 259.                                <GradientStop Color="#FF024572" 260.                                              Offset="0.836" /> 261.                                <GradientStop Color="#FF43ADF4" 262.                                              Offset="0.466" /> 263.                            </LinearGradientBrush> 264.                        </Path.Fill> 265.                    </Path> 266.                </telerik:RadToggleButton> 267.            </StackPanel > 268.            <Border DataContext="{TemplateBinding EditedAppointment}" 269.                    Background="{Binding Category.CategoryBrush}" 270.                    Visibility="{Binding Category,Converter={StaticResource NullToVisibilityConverter}}" 271.                    CornerRadius="3" 272.                    Height="20" 273.                    Margin="5,10,5,0"> 274.                <TextBlock Text="{Binding Category.DisplayName}" 275.                           VerticalAlignment="Center" 276.                           Margin="5,0,0,0" /> 277.            </Border> 278.            <Grid VerticalAlignment="Stretch" 279.                  HorizontalAlignment="Stretch" 280.                  DataContext="{TemplateBinding EditedAppointment}" 281.                  Background="{TemplateBinding Background}"> 282.                <Grid.RowDefinitions> 283.                    <RowDefinition Height="Auto" /> 284.                    <RowDefinition Height="Auto" /> 285.                    <RowDefinition Height="Auto" /> 286.                    <RowDefinition Height="Auto" /> 287.                    <RowDefinition Height="Auto" /> 288.                    <RowDefinition Height="Auto" /> 289.                    <RowDefinition Height="Auto" /> 290.                    <RowDefinition Height="Auto" /> 291.                    <RowDefinition Height="Auto" /> 292.                    <RowDefinition Height="Auto" /> 293.                </Grid.RowDefinitions> 294.                <Grid.ColumnDefinitions> 295.                    <ColumnDefinition Width="Auto" 296.                                      MinWidth="100" /> 297.                    <ColumnDefinition Width="Auto" 298.                                      MinWidth="200" /> 299.                </Grid.ColumnDefinitions> 300.                <!-- Subject --> 301.                <TextBlock x:Name="SubjectLabel" 302.                           Grid.Row="0" 303.                           Grid.Column="0" 304.                           Margin="0,15,0,2" 305.                           telerik:LocalizationManager.ResourceKey="Subject" 306.                           Style="{StaticResource FormElementTextBlockStyle}" /> 307.                <TextBox x:Name="Subject" 308.                         Grid.Row="0" 309.                         Grid.Column="1" 310.                         MinHeight="22" 311.                         Padding="4 2" 312.                         Width="340" 313.                         HorizontalAlignment="Left" 314.                         Text="{Binding Subject, Mode=TwoWay}" 315.                         MaxLength="255" 316.                         telerik:StyleManager.Theme="{StaticResource Theme}" 317.                         Margin="10,12,20,2" /> 318.                <!-- Description --> 319.                <TextBlock x:Name="DescriptionLabel" 320.                           Grid.Row="1" 321.                           Grid.Column="0" 322.                           Margin="0,13,0,2" 323.                           telerik:LocalizationManager.ResourceKey="Body" 324.                           Style="{StaticResource FormElementTextBlockStyle}" /> 325.                <TextBox x:Name="Body" 326.                         VerticalAlignment="top" 327.                         Grid.Row="1" 328.                         Grid.Column="1" 329.                         Height="Auto" 330.                         MaxHeight="82" 331.                         Width="340" 332.                         HorizontalAlignment="Left" 333.                         MinHeight="22" 334.                         Padding="4 2" 335.                         TextWrapping="Wrap" 336.                         telerik:StyleManager.Theme="{StaticResource Theme}" 337.                         Text="{Binding Body, Mode=TwoWay}" 338.                         AcceptsReturn="true" 339.                         Margin="10,10,20,2" 340.                         HorizontalScrollBarVisibility="Auto" 341.                         VerticalScrollBarVisibility="Auto" /> 342.                <!-- Start/End date --> 343.                <TextBlock x:Name="StartDateLabel" 344.                           Grid.Row="2" 345.                           Grid.Column="0" 346.                           Margin="0,13,0,2" 347.                           telerik:LocalizationManager.ResourceKey="StartTime" 348.                           Style="{StaticResource FormElementTextBlockStyle}" /> 349.                <telerikScheduler:DateTimePicker x:Name="StartDateTime" 350.                                                 Height="22" 351.                                                 Grid.Row="2" 352.                                                 Grid.Column="1" 353.                                                 HorizontalAlignment="Left" 354.                                                 Margin="10,10,20,2" 355.                                                 Style="{StaticResource FormElementStyle}" 356.                                                 SelectedDateTime="{Binding Start, Mode=TwoWay}" 357.                                                 telerikScheduler:StartEndDatePicker.EndPicker="{Binding ElementName=EndDateTime}" 358.                                                 IsTabStop="False" 359.                                                 IsEnabled="False" /> 360.                <TextBlock x:Name="EndDateLabel" 361.                           Grid.Row="3" 362.                           Grid.Column="0" 363.                           Margin="0,13,0,2" 364.                           telerik:LocalizationManager.ResourceKey="EndTime" 365.                           Style="{StaticResource FormElementTextBlockStyle}" /> 366.                <telerikScheduler:DateTimePicker x:Name="EndDateTime" 367.                                                 Height="22" 368.                                                 Grid.Row="3" 369.                                                 Grid.Column="1" 370.                                                 HorizontalAlignment="Left" 371.                                                 Margin="10,10,20,2" 372.                                                 Style="{StaticResource FormElementStyle}" 373.                                                 IsTabStop="False" 374.                                                 IsEnabled="False" 375.                                                 SelectedDateTime="{Binding End, Mode=TwoWay}" /> 376.                <!-- Is-all-day selector --> 377.                <CheckBox x:Name="AllDayEventCheckbox" 378.                          IsChecked="{Binding IsAllDayEvent, Mode=TwoWay}" 379.                          Grid.Row="4" 380.                          Grid.Column="1" 381.                          Margin="10,10,20,2" 382.                          HorizontalAlignment="Left" 383.                          telerik:StyleManager.Theme="{StaticResource Theme}" 384.                          telerik:LocalizationManager.ResourceKey="AllDayEvent"> 385.                    <telerik:CommandManager.InputBindings> 386.                        <telerik:InputBindingCollection> 387.                            <telerik:MouseBinding Command="telerikScheduler:RadSchedulerCommands.ChangeTimePickersVisibility" 388.                                                  Gesture="LeftClick" /> 389.                        </telerik:InputBindingCollection> 390.                    </telerik:CommandManager.InputBindings> 391.                </CheckBox> 392.                <Grid Grid.Row="5" 393.                      Grid.ColumnSpan="2"> 394.                    <Grid.ColumnDefinitions> 395.                        <ColumnDefinition Width="Auto" 396.                                          MinWidth="100" /> 397.                        <ColumnDefinition Width="Auto" 398.                                          MinWidth="200" /> 399.                    </Grid.ColumnDefinitions> 400.                    <Grid.RowDefinitions> 401.                        <RowDefinition Height="Auto" /> 402.                        <RowDefinition Height="Auto" /> 403.                    </Grid.RowDefinitions> 404.                    <TextBlock Text="Applicant" 405.                               Margin="0,13,0,2" 406.                               Style="{StaticResource FormElementTextBlockStyle}" /> 407.                    <telerikInput:RadComboBox IsEditable="False" 408.                                              Grid.Column="1" 409.                                              Height="24" 410.                                              VerticalAlignment="Center" 411.                                              ItemsSource="{Binding Source={StaticResource DataContextProxy}, Path=DataSource.ApplicantList}" 412.                                              SelectedValue="{Binding ApplicantID, Mode=TwoWay}" 413.                                              SelectedValuePath="ApplicantID" 414.                                              DisplayMemberPath="FirstName" /> 415.                       416.                    <TextBlock Text="Job" 417.                               Margin="0,13,0,2" 418.                               Grid.Row="1" 419.                               Style="{StaticResource FormElementTextBlockStyle}" /> 420.                    <telerikInput:RadComboBox IsEditable="False" 421.                                              Grid.Column="1" 422.                                              Grid.Row="1" 423.                                              Height="24" 424.                                              VerticalAlignment="Center" 425.                                              ItemsSource="{Binding Source={StaticResource DataContextProxy}, Path=DataSource.JobsList}" 426.                                              SelectedValue="{Binding PostingID, Mode=TwoWay}" 427.                                              SelectedValuePath="PostingID" 428.                                              DisplayMemberPath="JobTitle"/> 429.                </Grid> 430.                    <!-- Resources --> 431.                <Grid x:Name="ResourcesLayout" 432.                      Grid.Row="7" 433.                      Grid.Column="0" 434.                      Grid.ColumnSpan="2" 435.                      MaxHeight="130" 436.                      Margin="20,5,20,0"> 437.                    <Border Margin="0" 438.                            BorderThickness="1" 439.                            BorderBrush="{StaticResource GenericShallowBorderBrush}" 440.                            Visibility="{Binding ElementName=ResourcesScrollViewer, Path=ComputedVerticalScrollBarVisibility}"></Border> 441.                    <ScrollViewer x:Name="ResourcesScrollViewer" 442.                                  IsTabStop="false" 443.                                  Grid.Row="6" 444.                                  Grid.Column="0" 445.                                  Grid.ColumnSpan="2" 446.                                  Margin="1" 447.                                  telerik:StyleManager.Theme="{StaticResource Theme}" 448.                                  VerticalScrollBarVisibility="Auto"> 449.                        <scheduler:ResourcesItemsControl x:Name="PART_Resources" 450.                                                         HorizontalAlignment="Left" 451.                                                         Padding="0,2,0,5" 452.                                                         IsTabStop="false" 453.                                                         ItemsSource="{TemplateBinding ResourceTypeModels}" 454.                                                         ItemTemplateSelector="{StaticResource ItemTemplateSelector}" /> 455.                    </ScrollViewer> 456.                </Grid> 457.                <StackPanel x:Name="FooterControls" 458.                            Margin="5 10 10 10" 459.                            Grid.Row="8" 460.                            Grid.Column="1" 461.                            HorizontalAlignment="Left" 462.                            Orientation="Horizontal"> 463.                    <telerik:RadButton x:Name="OKButton" 464.                                       Margin="5" 465.                                       Padding="10 0" 466.                                       MinWidth="80" 467.                                       Command="telerikScheduler:RadSchedulerCommands.SaveAppointment" 468.                                       telerik:StyleManager.Theme="{StaticResource Theme}" 469.                                       telerikNavigation:RadWindow.ResponseButton="Accept" 470.                                       telerik:LocalizationManager.ResourceKey="SaveAndCloseCommandText"> 471.                    </telerik:RadButton> 472.                    <telerik:RadButton x:Name="CancelButton" 473.                                       Margin="5" 474.                                       Padding="10 0" 475.                                       MinWidth="80" 476.                                       telerik:LocalizationManager.ResourceKey="Cancel" 477.                                       telerik:StyleManager.Theme="{StaticResource Theme}" 478.                                       telerikNavigation:RadWindow.ResponseButton="Cancel" 479.                                       Command="telerik:WindowCommands.Close"> 480.                    </telerik:RadButton> 481.                </StackPanel> 482.            </Grid> 483.            <vsm:VisualStateManager.VisualStateGroups> 484.                <vsm:VisualStateGroup x:Name="RecurrenceRuleState"> 485.                    <vsm:VisualState x:Name="RecurrenceRuleIsNull"> 486.                        <Storyboard> 487.                            <ObjectAnimationUsingKeyFrames Storyboard.TargetName="StartDateTime" 488.                                                           Storyboard.TargetProperty="IsEnabled" 489.                                                           Duration="0"> 490.                                <DiscreteObjectKeyFrame KeyTime="0" 491.                                                        Value="True" /> 492.                            </ObjectAnimationUsingKeyFrames> 493.                            <ObjectAnimationUsingKeyFrames Storyboard.TargetName="EndDateTime" 494.                                                           Storyboard.TargetProperty="IsEnabled" 495.                                                           Duration="0"> 496.                                <DiscreteObjectKeyFrame KeyTime="0" 497.                                                        Value="True" /> 498.                            </ObjectAnimationUsingKeyFrames> 499.                            <ObjectAnimationUsingKeyFrames Storyboard.TargetName="AllDayEventCheckbox" 500.                                                           Storyboard.TargetProperty="IsEnabled" 501.                                                           Duration="0"> 502.                                <DiscreteObjectKeyFrame KeyTime="0" 503.                                                        Value="True" /> 504.                            </ObjectAnimationUsingKeyFrames> 505.                        </Storyboard> 506.                    </vsm:VisualState> 507.                    <vsm:VisualState x:Name="RecurrenceRuleIsNotNull"> 508.                        <Storyboard> 509.                            <ObjectAnimationUsingKeyFrames Storyboard.TargetName="StartDateTime" 510.                                                           Storyboard.TargetProperty="IsEnabled" 511.                                                           Duration="0"> 512.                                <DiscreteObjectKeyFrame KeyTime="0" 513.                                                        Value="False" /> 514.                            </ObjectAnimationUsingKeyFrames> 515.                            <ObjectAnimationUsingKeyFrames Storyboard.TargetName="EndDateTime" 516.                                                           Storyboard.TargetProperty="IsEnabled" 517.                                                           Duration="0"> 518.                                <DiscreteObjectKeyFrame KeyTime="0" 519.                                                        Value="False" /> 520.                            </ObjectAnimationUsingKeyFrames> 521.                            <ObjectAnimationUsingKeyFrames Storyboard.TargetName="AllDayEventCheckbox" 522.                                                           Storyboard.TargetProperty="IsEnabled" 523.                                                           Duration="0"> 524.                                <DiscreteObjectKeyFrame KeyTime="0" 525.                                                        Value="False" /> 526.                            </ObjectAnimationUsingKeyFrames> 527.                        </Storyboard> 528.                    </vsm:VisualState> 529.                </vsm:VisualStateGroup> 530.            </vsm:VisualStateManager.VisualStateGroups> 531.        </StackPanel> 532.    </ControlTemplate> 533.    <DataTemplate x:Key="AppointmentDialogWindowHeaderDataTemplate"> 534.        <StackPanel Orientation="Horizontal" 535.                    MaxWidth="400"> 536.            <TextBlock telerik:LocalizationManager.ResourceKey="Event" 537.                       Visibility="{Binding Appointment.IsAllDayEvent, Converter={StaticResource BooleanToVisibilityConverter}}" /> 538.            <TextBlock telerik:LocalizationManager.ResourceKey="Appointment" 539.                       Visibility="{Binding Appointment.IsAllDayEvent, Converter={StaticResource InvertedBooleanToVisibilityConverter}}" /> 540.            <TextBlock Text=" - " /> 541.            <TextBlock x:Name="SubjectTextBlock" 542.                       Visibility="{Binding Appointment.Subject, Converter={StaticResource NullToVisibilityConverter}}" 543.                       Text="{Binding Appointment.Subject}" /> 544.            <TextBlock telerik:LocalizationManager.ResourceKey="Untitled" 545.                       Visibility="{Binding Appointment.Subject, Converter={StaticResource InvertedNullToVisibilityConverter}}" /> 546.        </StackPanel> 547.    </DataTemplate> 548.    <Style x:Key="EditAppointmentStyle" 549.           TargetType="telerikScheduler:AppointmentDialogWindow"> 550.        <Setter Property="IconTemplate" 551.                Value="{StaticResource IconDataEditTemplate}" /> 552.        <Setter Property="HeaderTemplate" 553.                Value="{StaticResource AppointmentDialogWindowHeaderDataTemplate}" /> 554.        <Setter Property="Background" 555.                Value="{StaticResource DialogWindowBackground}" /> 556.        <Setter Property="Template" 557.                Value="{StaticResource EditAppointmentTemplate}" /> 558.    </Style> 559.</UserControl.Resources> The first line there is the DataContextProxy I mentioned previously- we use that again to work a bit of magic in this template. Where we start getting into the dialog in question is line 132, but line 407 is where things start getting interesting.  The ItemsSource is pointing at a list that exists in my ViewModel (or code-behind, if it is used as a DataContext), the SelectedValue is the item I am actually binding from the applicant (note the TwoWay binding), and SelectedValuePath and DisplayMemberPath ensure the proper applicant is being displayed from the collection.  You will also see similar starting on line 420 where I do the same for the Jobs we'll be displaying. Just to wrap-up the Xaml, here's the RadScheduler declaraction that ties this all together and will be the main focus of our view: 01.<telerikScheduler:RadScheduler x:Name="xJobsScheduler" 02.                  Grid.Row="1" 03.                  Grid.Column="1" 04.                  Width="800" 05.                  MinWidth="600" 06.                  Height="500" 07.                  MinHeight="300" 08.                  AppointmentsSource="{Binding Interviews}" 09.                  EditAppointmentStyle="{StaticResource EditAppointmentStyle}" 10.                  command:AppointmentAddedEventClass.Command="{Binding AddAppointmentCommand}" 11.                  command:ApptCreatedEventClass.Command="{Binding ApptCreatingCommand}" 12.                  command:ApptEditedEventClass.Command="{Binding ApptEditedCommand}" 13.                  command:ApptDeletedEventClass.Command="{Binding ApptDeletedCommand}"> 14.</telerikScheduler:RadScheduler> Now, we get to the ViewModel and what it takes to get that rigged up.  And for those of you who remember the jobs post, those command:s in the Xaml are pointing to attached behavior commands that reproduce the respective events.  This becomes very handy when we're setting up the code-behind version. ;) ViewModel I've been liking this approach so far, so I'm going to put the entire ViewModel here and then go into the lines of interest.  Of course, feel free to ask me questions about anything that isn't clear (by line number, ideally) so I can help out if I have missed anything important: 001.public class SchedulerViewModel : ViewModelBase 002.{ 003.    private readonly IEventAggregator eventAggregator; 004.    private readonly IRegionManager regionManager; 005.   006.    public RecruitingContext context; 007.   008.    private ObservableItemCollection<InterviewAppointment> _interviews = new ObservableItemCollection<InterviewAppointment>(); 009.    public ObservableItemCollection<InterviewAppointment> Interviews 010.    { 011.        get { return _interviews; } 012.        set 013.        { 014.            if (_interviews != value) 015.            { 016.                _interviews = value; 017.                NotifyChanged("Interviews"); 018.            } 019.        } 020.    } 021.   022.    private QueryableCollectionView _jobsList; 023.    public QueryableCollectionView JobsList 024.    { 025.        get { return this._jobsList; } 026.        set 027.        { 028.            if (this._jobsList != value) 029.            { 030.                this._jobsList = value; 031.                this.NotifyChanged("JobsList"); 032.            } 033.        } 034.    } 035.   036.    private QueryableCollectionView _applicantList; 037.    public QueryableCollectionView ApplicantList 038.    { 039.        get { return _applicantList; } 040.        set 041.        { 042.            if (_applicantList != value) 043.            { 044.                _applicantList = value; 045.                NotifyChanged("ApplicantList"); 046.            } 047.        } 048.    } 049.   050.    public DelegateCommand<object> AddAppointmentCommand { get; set; } 051.    public DelegateCommand<object> ApptCreatingCommand { get; set; } 052.    public DelegateCommand<object> ApptEditedCommand { get; set; } 053.    public DelegateCommand<object> ApptDeletedCommand { get; set; } 054.   055.    public SchedulerViewModel(IEventAggregator eventAgg, IRegionManager regionmanager) 056.    { 057.        // set Unity items 058.        this.eventAggregator = eventAgg; 059.        this.regionManager = regionmanager; 060.   061.        // load our context 062.        context = new RecruitingContext(); 063.        LoadOperation<Interview> loadOp = context.Load(context.GetInterviewsQuery()); 064.        loadOp.Completed += new EventHandler(loadOp_Completed); 065.   066.        this._jobsList = new QueryableCollectionView(context.JobPostings); 067.        context.Load(context.GetJobPostingsQuery()); 068.   069.        this._applicantList = new QueryableCollectionView(context.Applicants); 070.        context.Load(context.GetApplicantsQuery()); 071.   072.        AddAppointmentCommand = new DelegateCommand<object>(this.AddAppt); 073.        ApptCreatingCommand = new DelegateCommand<object>(this.ApptCreating); 074.        ApptEditedCommand = new DelegateCommand<object>(this.ApptEdited); 075.        ApptDeletedCommand = new DelegateCommand<object>(this.ApptDeleted); 076.   077.    } 078.   079.    void loadOp_Completed(object sender, EventArgs e) 080.    { 081.        LoadOperation loadop = sender as LoadOperation; 082.   083.        foreach (var ent in loadop.Entities) 084.        { 085.            _interviews.Add(EntityToAppointment(ent as Interview)); 086.        } 087.    } 088.   089.    #region Appointment Adding 090.   091.    public void AddAppt(object obj) 092.    { 093.        // now we have a new InterviewAppointment to add to our QCV :) 094.        InterviewAppointment newInterview = obj as InterviewAppointment; 095.   096.        this.context.Interviews.Add(AppointmentToEntity(newInterview)); 097.        this.context.SubmitChanges((s) => 098.        { 099.            ActionHistory myAction = new ActionHistory(); 100.            myAction.InterviewID = newInterview.InterviewID; 101.            myAction.PostingID = newInterview.PostingID; 102.            myAction.ApplicantID = newInterview.ApplicantID; 103.            myAction.Description = String.Format("Interview with {0} has been created by {1}", newInterview.ApplicantID.ToString(), "default user"); 104.            myAction.TimeStamp = DateTime.Now; 105.            eventAggregator.GetEvent<AddActionEvent>().Publish(myAction); 106.        } 107.            , null); 108.    } 109.   110.    public void ApptCreating(object obj) 111.    { 112.        // handled in the behavior, just a placeholder to ensure it runs :) 113.    } 114.   115.    #endregion 116.   117.    #region Appointment Editing 118.   119.    public void ApptEdited(object obj) 120.    { 121.        Interview editedInterview = (from x in context.Interviews 122.                            where x.InterviewID == (obj as InterviewAppointment).InterviewID 123.                            select x).SingleOrDefault(); 124.   125.        CopyAppointmentEdit(editedInterview, obj as InterviewAppointment); 126.   127.        context.SubmitChanges((s) => { 128.            ActionHistory myAction = new ActionHistory(); 129.            myAction.InterviewID = editedInterview.InterviewID; 130.            myAction.PostingID = editedInterview.PostingID; 131.            myAction.ApplicantID = editedInterview.ApplicantID; 132.            myAction.Description = String.Format("Interview with {0} has been modified by {1}", editedInterview.ApplicantID.ToString(), "default user"); 133.            myAction.TimeStamp = DateTime.Now; 134.            eventAggregator.GetEvent<AddActionEvent>().Publish(myAction); } 135.            , null); 136.    } 137.   138.    #endregion 139.   140.    #region Appointment Deleting 141.   142.    public void ApptDeleted(object obj) 143.    { 144.        Interview deletedInterview = (from x in context.Interviews 145.                                      where x.InterviewID == (obj as InterviewAppointment).InterviewID 146.                                      select x).SingleOrDefault(); 147.   148.        context.Interviews.Remove(deletedInterview); 149.        context.SubmitChanges((s) => 150.        { 151.            ActionHistory myAction = new ActionHistory(); 152.            myAction.InterviewID = deletedInterview.InterviewID; 153.            myAction.PostingID = deletedInterview.PostingID; 154.            myAction.ApplicantID = deletedInterview.ApplicantID; 155.            myAction.Description = String.Format("Interview with {0} has been deleted by {1}", deletedInterview.ApplicantID.ToString(), "default user"); 156.            myAction.TimeStamp = DateTime.Now; 157.            eventAggregator.GetEvent<AddActionEvent>().Publish(myAction); 158.        } 159.            , null); 160.    } 161.   162.    #endregion 163.   164.    #region Appointment Helpers :) 165.   166.    public Interview AppointmentToEntity(InterviewAppointment ia) 167.    { 168.        Interview newInterview = new Interview(); 169.        newInterview.Subject = ia.Subject; 170.        newInterview.Body = ia.Body; 171.        newInterview.Start = ia.Start; 172.        newInterview.End = ia.End; 173.        newInterview.ApplicantID = ia.ApplicantID; 174.        newInterview.PostingID = ia.PostingID; 175.        newInterview.InterviewID = ia.InterviewID; 176.   177.        return newInterview; 178.    } 179.   180.    public InterviewAppointment EntityToAppointment(Interview ia) 181.    { 182.        InterviewAppointment newInterview = new InterviewAppointment(); 183.        newInterview.Subject = ia.Subject; 184.        newInterview.Body = ia.Body; 185.        newInterview.Start = ia.Start; 186.        newInterview.End = ia.End; 187.        newInterview.ApplicantID = ia.ApplicantID; 188.        newInterview.PostingID = ia.PostingID; 189.        newInterview.InterviewID = ia.InterviewID; 190.   191.        return newInterview; 192.    } 193.   194.    public void CopyAppointmentEdit(Interview entityInterview, InterviewAppointment appointmentInterview) 195.    { 196.        entityInterview.Subject = appointmentInterview.Subject; 197.        entityInterview.Body = appointmentInterview.Body; 198.        entityInterview.Start = appointmentInterview.Start; 199.        entityInterview.End = appointmentInterview.End; 200.        entityInterview.ApplicantID = appointmentInterview.ApplicantID; 201.        entityInterview.PostingID = appointmentInterview.PostingID; 202.    } 203.   204.    #endregion 205.} One thing we're doing here which you won't see in any of the other ViewModels is creating a duplicate collection.  I know this is something which will be fixed down the line for using RadScheduler, simplifying this process, but with WCF RIA changing as it does I wanted to ensure functionality would remain consistent as I continued development on this application.  So, I do a little bit of duplication, but for the greater good.  This all takes place starting on line 79, so for every entity that comes back we add it to the collection that is bound to RadScheduler.  Otherwise, the DelegateCommands that you see correspond directly to the events they are named after.  In each case, rather than sending over the full event arguments, I just send in the appointment in question (coming through as the object obj in all cases) so I can add (line 91), edit (line 119), and delete appointments (line 142) like normal.  This just ensures they get updated back to my database.  Also, the one bit of code you won't see is for the Appointment Creating (line 110) event- that is because in the command I've created I simply make the replacement I need to: 1.void element_AppointmentCreating(object sender, AppointmentCreatingEventArgs e) 2.{ 3.    e.NewAppointment = new InterviewAppointment(); 4.    base.ExecuteCommand(); 5.} And the ViewModel is none the wiser, the appointments just work as far as it is concerned since as they are created they become InterviewAppointments.  End result?  I've customized my EditAppointmentDialog as follows: And adding, editing, and deleting appointments works like a charm.  I can even 'edit' by moving appointments around RadScheduler, so as they are dropped into a timeslot they perform their full edit routine and things get updated. And then, the Code-Behind Version Perhaps the thing I like the most about doing one then the other is I get to steal 90% or more of the code from the MVVM version.  For example, the only real changes to the Code-Behind Xaml file exist in the control declaration, in which I use events instead of attached-behavior-event-commands: 01.<telerikScheduler:RadScheduler x:Name="xJobsScheduler" 02.                  Grid.Row="1" 03.                  Grid.Column="1" 04.                  Width="800" 05.                  MinWidth="600" 06.                  Height="500" 07.                  MinHeight="300" 08.                  EditAppointmentStyle="{StaticResource EditAppointmentStyle}" 09.                  AppointmentAdded="xJobsScheduler_AppointmentAdded" 10.                  AppointmentCreating="xJobsScheduler_AppointmentCreating" 11.                  AppointmentEdited="xJobsScheduler_AppointmentEdited" 12.                  AppointmentDeleted="xJobsScheduler_AppointmentDeleted"> 13.</telerikScheduler:RadScheduler> Easy, right?  Otherwise, all the same styling in UserControl.Resources was re-used, right down to the DataContextProxy that lets us bind to a collection from our viewmodel (in this case, our code-behind) to use within the DataTemplate.  The code conversion gets even easier, as I could literally copy and paste almost everything from the ViewModel to my Code-Behind, just a matter of pasting the right section into the right event.  Here's the code-behind as proof: 001.public partial class SchedulingView : UserControl, INotifyPropertyChanged 002.{ 003.    public RecruitingContext context; 004.   005.    private QueryableCollectionView _jobsList; 006.    public QueryableCollectionView JobsList 007.    { 008.        get { return this._jobsList; } 009.        set 010.        { 011.            if (this._jobsList != value) 012.            { 013.                this._jobsList = value; 014.                this.NotifyChanged("JobsList"); 015.            } 016.        } 017.    } 018.   019.    private QueryableCollectionView _applicantList; 020.    public QueryableCollectionView ApplicantList 021.    { 022.        get { return _applicantList; } 023.        set 024.        { 025.            if (_applicantList != value) 026.            { 027.                _applicantList = value; 028.                NotifyChanged("ApplicantList"); 029.            } 030.        } 031.    } 032.   033.    private ObservableItemCollection<InterviewAppointment> _interviews = new ObservableItemCollection<InterviewAppointment>(); 034.    public ObservableItemCollection<InterviewAppointment> Interviews 035.    { 036.        get { return _interviews; } 037.        set 038.        { 039.            if (_interviews != value) 040.            { 041.                _interviews = value; 042.                NotifyChanged("Interviews"); 043.            } 044.        } 045.    } 046.   047.    public SchedulingView() 048.    { 049.        InitializeComponent(); 050.   051.        this.DataContext = this; 052.   053.        this.Loaded += new RoutedEventHandler(SchedulingView_Loaded); 054.    } 055.   056.    void SchedulingView_Loaded(object sender, RoutedEventArgs e) 057.    { 058.        this.xJobsScheduler.AppointmentsSource = Interviews; 059.   060.        context = new RecruitingContext(); 061.           062.        LoadOperation loadop = context.Load(context.GetInterviewsQuery()); 063.        loadop.Completed += new EventHandler(loadop_Completed); 064.   065.        this._applicantList = new QueryableCollectionView(context.Applicants); 066.        context.Load(context.GetApplicantsQuery()); 067.   068.        this._jobsList = new QueryableCollectionView(context.JobPostings); 069.        context.Load(context.GetJobPostingsQuery()); 070.    } 071.   072.    void loadop_Completed(object sender, EventArgs e) 073.    { 074.        LoadOperation loadop = sender as LoadOperation; 075.   076.        _interviews.Clear(); 077.   078.        foreach (var ent in loadop.Entities) 079.        { 080.            _interviews.Add(EntityToAppointment(ent as Interview)); 081.        } 082.    } 083.   084.    private void xJobsScheduler_AppointmentAdded(object sender, Telerik.Windows.Controls.AppointmentAddedEventArgs e) 085.    { 086.        // now we have a new InterviewAppointment to add to our QCV :) 087.        InterviewAppointment newInterview = e.Appointment as InterviewAppointment; 088.   089.        this.context.Interviews.Add(AppointmentToEntity(newInterview)); 090.        this.context.SubmitChanges((s) => 091.        { 092.            ActionHistory myAction = new ActionHistory(); 093.            myAction.InterviewID = newInterview.InterviewID; 094.            myAction.PostingID = newInterview.PostingID; 095.            myAction.ApplicantID = newInterview.ApplicantID; 096.            myAction.Description = String.Format("Interview with {0} has been created by {1}", newInterview.ApplicantID.ToString(), "default user"); 097.            myAction.TimeStamp = DateTime.Now; 098.            context.ActionHistories.Add(myAction); 099.            context.SubmitChanges(); 100.        } 101.            , null); 102.    } 103.   104.    private void xJobsScheduler_AppointmentCreating(object sender, Telerik.Windows.Controls.AppointmentCreatingEventArgs e) 105.    { 106.        e.NewAppointment = new InterviewAppointment(); 107.    } 108.   109.    private void xJobsScheduler_AppointmentEdited(object sender, Telerik.Windows.Controls.AppointmentEditedEventArgs e) 110.    { 111.        Interview editedInterview = (from x in context.Interviews 112.                                     where x.InterviewID == (e.Appointment as InterviewAppointment).InterviewID 113.                                     select x).SingleOrDefault(); 114.   115.        CopyAppointmentEdit(editedInterview, e.Appointment as InterviewAppointment); 116.   117.        context.SubmitChanges((s) => 118.        { 119.            ActionHistory myAction = new ActionHistory(); 120.            myAction.InterviewID = editedInterview.InterviewID; 121.            myAction.PostingID = editedInterview.PostingID; 122.            myAction.ApplicantID = editedInterview.ApplicantID; 123.            myAction.Description = String.Format("Interview with {0} has been modified by {1}", editedInterview.ApplicantID.ToString(), "default user"); 124.            myAction.TimeStamp = DateTime.Now; 125.            context.ActionHistories.Add(myAction); 126.            context.SubmitChanges(); 127.        } 128.            , null); 129.    } 130.   131.    private void xJobsScheduler_AppointmentDeleted(object sender, Telerik.Windows.Controls.AppointmentDeletedEventArgs e) 132.    { 133.        Interview deletedInterview = (from x in context.Interviews 134.                                      where x.InterviewID == (e.Appointment as InterviewAppointment).InterviewID 135.                                      select x).SingleOrDefault(); 136.   137.        context.Interviews.Remove(deletedInterview); 138.        context.SubmitChanges((s) => 139.        { 140.            ActionHistory myAction = new ActionHistory(); 141.            myAction.InterviewID = deletedInterview.InterviewID; 142.            myAction.PostingID = deletedInterview.PostingID; 143.            myAction.ApplicantID = deletedInterview.ApplicantID; 144.            myAction.Description = String.Format("Interview with {0} has been deleted by {1}", deletedInterview.ApplicantID.ToString(), "default user"); 145.            myAction.TimeStamp = DateTime.Now; 146.            context.ActionHistories.Add(myAction); 147.            context.SubmitChanges(); 148.        } 149.            , null); 150.    } 151.   152.    #region Appointment Helpers :) 153.   154.    public Interview AppointmentToEntity(InterviewAppointment ia) 155.    { 156.        Interview newInterview = new Interview(); 157.        newInterview.Subject = ia.Subject; 158.        newInterview.Body = ia.Body; 159.        newInterview.Start = ia.Start; 160.        newInterview.End = ia.End; 161.        newInterview.ApplicantID = ia.ApplicantID; 162.        newInterview.PostingID = ia.PostingID; 163.        newInterview.InterviewID = ia.InterviewID; 164.   165.        return newInterview; 166.    } 167.   168.    public InterviewAppointment EntityToAppointment(Interview ia) 169.    { 170.        InterviewAppointment newInterview = new InterviewAppointment(); 171.        newInterview.Subject = ia.Subject; 172.        newInterview.Body = ia.Body; 173.        newInterview.Start = ia.Start; 174.        newInterview.End = ia.End; 175.        newInterview.ApplicantID = ia.ApplicantID; 176.        newInterview.PostingID = ia.PostingID; 177.        newInterview.InterviewID = ia.InterviewID; 178.   179.        return newInterview; 180.    } 181.   182.    public void CopyAppointmentEdit(Interview entityInterview, InterviewAppointment appointmentInterview) 183.    { 184.        entityInterview.Subject = appointmentInterview.Subject; 185.        entityInterview.Body = appointmentInterview.Body; 186.        entityInterview.Start = appointmentInterview.Start; 187.        entityInterview.End = appointmentInterview.End; 188.        entityInterview.ApplicantID = appointmentInterview.ApplicantID; 189.        entityInterview.PostingID = appointmentInterview.PostingID; 190.    } 191.   192.    #endregion 193.   194.    #region INotifyPropertyChanged Members 195.   196.    public event PropertyChangedEventHandler PropertyChanged; 197.   198.    public void NotifyChanged(string propertyName) 199.    { 200.        if (string.IsNullOrEmpty(propertyName)) 201.            throw new ArgumentException("propertyName"); 202.   203.        PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); 204.    } 205.   206.    #endregion 207.} Nice... right? :) One really important thing to note as well.  See on line 51 where I set the DataContext before the Loaded event?  This is super important, as if you don't have this set before the usercontrol is loaded, the DataContextProxy has no context to use and your EditAppointmentDialog Job/Applicant dropdowns will be blank and empty.  Trust me on this, took a little bit of debugging to figure out that by setting the DataContext post-loaded would only lead to disaster and frustration.  Otherwise, the only other real difference is that instead of sending an ActionHistory item through an event to get added to the database and saved, I do those right in the callback from submitting.  The Result Again, I only have to post one picture because these bad boys used nearly identical code for both the MVVM and the code-behind views, so our end result is... So what have we learned here today?  One, for the most part this MVVM thing is somewhat easy.  Yeah, you sometimes have to write a bunch of extra code, but with the help of a few useful snippits you can turn the process into a pretty streamlined little workflow.  Heck, this story gets even easier as you can see in this blog post by Michael Washington- specifically run a find on 'InvokeCommandAction' and you'll see the section regarding the command on TreeView in Blend 4.  Brilliant!  MVVM never looked so sweet! Otherwise, it is business as usual with RadScheduler for Silverlight whichever path you're choosing for your development.  Between now and the next post, I'll be cleaning up styles a bit (those RadComboBoxes are a little too close to my labels!) and adding some to the RowDetailsViews for Applicants and Jobs, so you can see all the info for an appointment in the dropdown tab view.  Otherwise, we're about ready to call a wrap on this oneDid you know that DotNetSlackers also publishes .net articles written by top known .net Authors? We already have over 80 articles in several categories including Silverlight. Take a look: here.

    Read the article

< Previous Page | 76 77 78 79 80