Skip to content

Commit 54aff51

Browse files
committed
Extended the framework to support DataTemplateSelectors.
Now, DataTemplateSelectors can be created using lambda functions in similar manner as the ValueConverters and MultiValueConverters. Unlike the converters, the selectors have no interfaces to implement against. Hence, in the implementation the created classes had to derive from the DataTemplateSelector base class. However, this is only an internal implementation detail and won't bother the consumers of this framework too much as it will make minor differences in usage compared to the converters. Signed-off-by: Dima Enns <[email protected]>
1 parent be0a05a commit 54aff51

10 files changed

+373
-1
lines changed

Sources/LambdaConverters.Wpf/EventSource.cs

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ internal sealed class EventSource : System.Diagnostics.Tracing.EventSource
1313
public static class Keywords
1414
{
1515
public const EventKeywords Converters = (EventKeywords)0x4;
16+
public const EventKeywords Selectors = (EventKeywords)0x8;
1617
}
1718

1819
[NotNull]
@@ -451,5 +452,63 @@ public void UnableToCastToParameterTypeForBackConversion(
451452
memberName,
452453
sourceFilePath,
453454
sourceLineNumber);
455+
456+
/// <summary>
457+
/// The {0} is null, conversion result is a value according to the specified error strategy ({1}).
458+
/// </summary>
459+
[Conditional("TRACE")]
460+
[Event(
461+
22,
462+
Version = 0,
463+
Message = "The {0} is null, conversion result is a value according to the specified error strategy ({1}).",
464+
Level = EventLevel.Warning,
465+
Keywords = Keywords.Selectors,
466+
Opcode = EventOpcode.Info,
467+
Channel = EventChannel.Operational
468+
)]
469+
public void MissingSelectTemplateFunction(
470+
string callback,
471+
string errorStrategy,
472+
[CallerMemberName] string memberName = null,
473+
[CallerFilePath] string sourceFilePath = null,
474+
[CallerLineNumber] int sourceLineNumber = 0)
475+
=>
476+
WriteEvent(
477+
22,
478+
callback,
479+
errorStrategy,
480+
memberName,
481+
sourceFilePath,
482+
sourceLineNumber);
483+
484+
/// <summary>
485+
/// The value ({0}) cannot be cast to the specified input type ({1}), conversion result is a value according to the specified error strategy ({2}).
486+
/// </summary>
487+
[Conditional("TRACE")]
488+
[Event(
489+
23,
490+
Version = 0,
491+
Message = "The value ({0}) cannot be cast to the specified input type ({1}), conversion result is a value according to the specified error strategy ({2}).",
492+
Level = EventLevel.Warning,
493+
Keywords = Keywords.Selectors,
494+
Opcode = EventOpcode.Info,
495+
Channel = EventChannel.Operational
496+
)]
497+
public void UnableToCastToItemType(
498+
string itemType,
499+
string inputType,
500+
string errorStrategy,
501+
[CallerMemberName] string memberName = null,
502+
[CallerFilePath] string sourceFilePath = null,
503+
[CallerLineNumber] int sourceLineNumber = 0)
504+
=>
505+
WriteEvent(
506+
23,
507+
itemType,
508+
inputType,
509+
errorStrategy,
510+
memberName,
511+
sourceFilePath,
512+
sourceLineNumber);
454513
}
455514
}

Sources/LambdaConverters.Wpf/LambdaConverters.Wpf.csproj

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,9 @@
5757
<Reference Include="WindowsBase" />
5858
</ItemGroup>
5959
<ItemGroup>
60+
<Compile Include="Selector.cs" />
6061
<Compile Include="Converter.cs" />
62+
<Compile Include="SelectorErrorStrategy.cs" />
6163
<Compile Include="ConverterErrorStrategy.cs" />
6264
<Compile Include="EventSource.cs" />
6365
<Compile Include="MultiValueConverter.cs" />
@@ -66,13 +68,17 @@
6668
<Compile Include="MultiValueConverterArgs[T].cs" />
6769
<Compile Include="MultiValueConverterArgs[T].IEquatable[MultiValueConverterArgs[T]].cs" />
6870
<Compile Include="Properties\AssemblyInfo.cs" />
71+
<Compile Include="TemplateSelector.cs" />
6972
<Compile Include="ValueConverter.cs" />
7073
<Compile Include="ValueConverterArgs[T,P].cs" />
7174
<Compile Include="ValueConverterArgs[T,P].IEquatable[ValueConverterArgs[T,P]].cs" />
75+
<Compile Include="TemplateSelectorArgs[T].cs" />
7276
<Compile Include="ValueConverterArgs[T].cs" />
77+
<Compile Include="TemplateSelectorArgs[T].IEquatable[DataTemplateSelectorArgs[T]].cs" />
7378
<Compile Include="ValueConverterArgs[T].IEquatable[ValueConverterArgs[T]].cs" />
7479
</ItemGroup>
7580
<ItemGroup>
81+
<None Include="ClassDiagram1.cd" />
7682
<None Include="packages.config" />
7783
<None Include="Properties\LambdaConverters.public.snk" />
7884
</ItemGroup>

Sources/LambdaConverters.Wpf/Properties/AssemblyInfo.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
[assembly: AssemblyConfiguration("")]
1010
[assembly: AssemblyCompany("")]
1111
[assembly: AssemblyProduct("LambdaConverters")]
12-
[assembly: AssemblyCopyright(2016 Michael Damatov.")]
12+
[assembly: AssemblyCopyright(2017 Michael Damatov.")]
1313
[assembly: AssemblyTrademark("")]
1414
[assembly: AssemblyCulture("")]
1515

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
using System;
2+
using System.Windows;
3+
using JetBrains.Annotations;
4+
5+
namespace LambdaConverters
6+
{
7+
internal abstract class Selector : System.Windows.Controls.DataTemplateSelector
8+
{
9+
internal Selector(
10+
SelectorErrorStrategy errorStrategy,
11+
object defaultInputTypeValue,
12+
[NotNull] Type inputType,
13+
bool isConvertFunctionAvailable)
14+
{
15+
ErrorStrategy = errorStrategy;
16+
DefaultInputTypeValue = defaultInputTypeValue;
17+
InputType = inputType;
18+
IsSelectFunctionAvailable = isConvertFunctionAvailable;
19+
}
20+
21+
internal SelectorErrorStrategy ErrorStrategy { get; }
22+
23+
internal object DefaultInputTypeValue { get; }
24+
25+
[NotNull]
26+
internal Type InputType { get; }
27+
28+
internal bool IsSelectFunctionAvailable { get; }
29+
30+
[Pure]
31+
internal DataTemplate GetErrorValue(DataTemplate defaultValue)
32+
{
33+
switch (ErrorStrategy)
34+
{
35+
case SelectorErrorStrategy.ReturnDefaultValue:
36+
return defaultValue;
37+
38+
case SelectorErrorStrategy.ReturnNewEmptyDataTemplate:
39+
return new DataTemplate();
40+
41+
default:
42+
throw new NotSupportedException();
43+
}
44+
}
45+
}
46+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
using System.Windows.Controls;
2+
using System.Windows.Data;
3+
4+
namespace LambdaConverters
5+
{
6+
/// <summary>
7+
/// Defines the selector error strategy.
8+
/// </summary>
9+
public enum SelectorErrorStrategy
10+
{
11+
/// <summary>
12+
/// Null is returned.
13+
/// </summary>
14+
ReturnDefaultValue,
15+
16+
/// <summary>
17+
/// A new and empty data template is returned.
18+
/// </summary>
19+
ReturnNewEmptyDataTemplate
20+
}
21+
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
using System;
2+
using System.Diagnostics;
3+
using System.Windows;
4+
using System.Windows.Controls;
5+
using JetBrains.Annotations;
6+
7+
namespace LambdaConverters
8+
{
9+
/// <summary>
10+
/// A factory class used to create lambda-based instances of the <see cref="DataTemplateSelector"/> class.
11+
/// </summary>
12+
public static class TemplateSelector
13+
{
14+
abstract class Selector : LambdaConverters.Selector
15+
{
16+
protected Selector(
17+
SelectorErrorStrategy errorStrategy,
18+
object defaultInputTypeValue,
19+
[NotNull] Type inputType,
20+
bool isSelectorFunctionAvailable)
21+
: base(
22+
errorStrategy,
23+
defaultInputTypeValue,
24+
inputType,
25+
isSelectorFunctionAvailable) { }
26+
27+
protected abstract DataTemplate SelectTemplateInternal(object item, DependencyObject container);
28+
29+
public override DataTemplate SelectTemplate(object item, DependencyObject container)
30+
{
31+
if (!IsSelectFunctionAvailable)
32+
{
33+
EventSource.Log.MissingSelectTemplateFunction("selectFunction", ErrorStrategy.ToString());
34+
35+
return GetErrorValue(default(DataTemplate));
36+
}
37+
38+
return SelectTemplateInternal(item, container);
39+
}
40+
}
41+
42+
sealed class Selector<I> : Selector
43+
{
44+
readonly Func<TemplateSelectorArgs<I>, DataTemplate> selectFunction;
45+
46+
internal Selector(
47+
Func<TemplateSelectorArgs<I>, DataTemplate> selectFunction,
48+
SelectorErrorStrategy errorStrategy)
49+
: base(errorStrategy, default(I), typeof(I), selectFunction != null)
50+
{
51+
this.selectFunction = selectFunction;
52+
}
53+
54+
protected override DataTemplate SelectTemplateInternal(object item, DependencyObject container)
55+
{
56+
I inputValue;
57+
try
58+
{
59+
inputValue = (I)item;
60+
}
61+
catch (SystemException e) when (e is InvalidCastException || e is NullReferenceException)
62+
{
63+
EventSource.Log.UnableToCastToInputType(item?.GetType().Name ?? "null", typeof(I).Name, ErrorStrategy.ToString());
64+
65+
return GetErrorValue(default(DataTemplate));
66+
}
67+
68+
Debug.Assert(selectFunction != null);
69+
70+
return selectFunction(new TemplateSelectorArgs<I>(inputValue, container));
71+
}
72+
}
73+
74+
/// <summary>
75+
/// Initializes a new instance of the <see cref="DataTemplateSelector" /> interface.
76+
/// </summary>
77+
/// <typeparam name="I">The item type.</typeparam>
78+
/// <param name="selectFunction">The <see cref="DataTemplateSelector.SelectTemplate" /> method.</param>
79+
/// <param name="errorStrategy">The error strategy.</param>
80+
/// <returns>An <see cref="DataTemplateSelector" /> object.</returns>
81+
/// <exception cref="ArgumentOutOfRangeException">
82+
/// <paramref name="errorStrategy"/> is not a valid <see cref="SelectorErrorStrategy"/> value.
83+
/// </exception>
84+
[Pure]
85+
[NotNull]
86+
public static DataTemplateSelector Create<I>(
87+
Func<TemplateSelectorArgs<I>, DataTemplate> selectFunction = null,
88+
SelectorErrorStrategy errorStrategy = SelectorErrorStrategy.ReturnDefaultValue)
89+
{
90+
switch (errorStrategy)
91+
{
92+
case SelectorErrorStrategy.ReturnDefaultValue:
93+
case SelectorErrorStrategy.ReturnNewEmptyDataTemplate:
94+
break;
95+
96+
default:
97+
throw new ArgumentOutOfRangeException(nameof(errorStrategy));
98+
}
99+
100+
return new Selector<I>(selectFunction, errorStrategy);
101+
}
102+
}
103+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
using System;
2+
using System.Collections.Generic;
3+
4+
namespace LambdaConverters
5+
{
6+
partial struct TemplateSelectorArgs<T> : IEquatable<TemplateSelectorArgs<T>>
7+
{
8+
/// <summary>
9+
/// Implements the operator <c>==</c>.
10+
/// </summary>
11+
/// <param name="x">The left operand.</param>
12+
/// <param name="y">The right operand.</param>
13+
/// <returns>The result of the operator.</returns>
14+
public static bool operator ==(TemplateSelectorArgs<T> x, TemplateSelectorArgs<T> y) => Equals(x.Item, y.Item) && Equals(x.Container, y.Container);
15+
16+
/// <summary>
17+
/// Implements the operator <c>!=</c>.
18+
/// </summary>
19+
/// <param name="x">The left operand.</param>
20+
/// <param name="y">The right operand.</param>
21+
/// <returns>The result of the operator.</returns>
22+
public static bool operator !=(TemplateSelectorArgs<T> x, TemplateSelectorArgs<T> y) => !Equals(x.Item, y.Item) || !Equals(x.Container, y.Container);
23+
24+
/// <inheritdoc />
25+
public override int GetHashCode() => EqualityComparer<T>.Default.GetHashCode(Item) ^ (Container?.GetHashCode() ?? 0);
26+
27+
/// <inheritdoc />
28+
public override bool Equals(object obj) => obj is TemplateSelectorArgs<T> && Equals((TemplateSelectorArgs<T>)obj);
29+
30+
/// <inheritdoc />
31+
public bool Equals(TemplateSelectorArgs<T> other) => this == other;
32+
}
33+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
using System.Windows;
2+
3+
namespace LambdaConverters
4+
{
5+
/// <summary>
6+
/// Provides data for selecting data templates.
7+
/// </summary>
8+
/// <typeparam name="T">The item type.</typeparam>
9+
public partial struct TemplateSelectorArgs<T>
10+
{
11+
internal TemplateSelectorArgs(T item, DependencyObject container)
12+
{
13+
Item = item;
14+
Container = container;
15+
}
16+
17+
/// <summary>
18+
/// Gets the item.
19+
/// </summary>
20+
public T Item { get; }
21+
22+
/// <summary>
23+
/// Gets the container.
24+
/// </summary>
25+
public DependencyObject Container { get; }
26+
}
27+
}

0 commit comments

Comments
 (0)