Silverlight Recruiting Application Part 6 - Adding an Interview Scheduling Module/View
Posted
on Dot net Slackers
See other posts from Dot net Slackers
Published on Mon, 29 Mar 2010 00:00:00 GMT
Indexed on
2010/03/30
22:23 UTC
Read the original article
Hit count: 686
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 one
Did 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.
© Dot net Slackers or respective owner