我有一个Windows Phone 8.1 Silverlight应用程序,其中我有一个包含多个属性的模型,如下所示(只有3个属性的摘录,它还有更多)。
public class WorkUnit : INotifyPropertyChanged
{
public DateTime? To
{
get { return Get<DateTime?>(); }
set
{
Set(value);
OnPropertyChanged("To");
OnPropertyChanged("ToAsShortTimeString");
}
}
public string ToAsShortTimeString
{
get
{
if (To.HasValue)
{
if (Type == WorkUnitType.StartEnd)
return To.Value.ToString(CultureInfo.CurrentCulture.DateTimeFormat.ShortTimePattern);
var duration = To.Value - From;
return DateHelper.FormatTime(duration, false);
}
return null;
}
}
public short? Type
{
get { return Get<short?>(); }
set
{
Set(value);
OnPropertyChanged("Type");
}
}
}
我正在使用MVVMLight。 ObservableCollection中有多个工作单元绑定到Windows Phone页面上的列表框。集合本身是(WorkDay)视图模型的一部分,而模型又与页面本身绑定。
我的模型中有很多属性,仅用于格式化UI的某些属性。其中一个是ToAsShortTimeString,它返回 To 属性给出的时间,具体取决于 Type 和 From 属性,格式为字符串。
为了清理我的模型,我想尽可能地删除这些格式化程序属性,并尽可能使用转换器(IValueConverter)。远离这些属性的另一个原因是我使用的数据库(iBoxDB)没有像SQLite可用的[Ignore]等成员属性。因此,所有具有受支持类型的属性都存储在数据库中。但是,如果可能,不应存储此类格式化程序属性。
我现在将所有属性转换为转换器,大部分时间这都没有问题。但是, ToAsShortTimeString 不仅使用一个属性而且使用3来格式化输入。因此,在XAML中,我需要向值转换器或绑定到页面的工作单元本身提供这3个属性。
public class WorkUnitToEndTimeStringConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var workUnit = (WorkUnit) value;
if (workUnit.To.HasValue)
{
if (workUnit.Type == WorkUnitType.StartEnd)
return workUnit.To.Value.ToString(CultureInfo.CurrentCulture.DateTimeFormat.ShortTimePattern);
var duration = workUnit.To.Value - workUnit.From;
return DateHelper.FormatTime(duration, false);
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
所以我更改了TextBlock中 Text 属性的绑定,该属性将格式化的 To 属性显示给绑定到页面的WorkUnit。
<TextBlock
Grid.Column="2" Grid.Row="0"
Grid.ColumnSpan="2"
Text="{Binding WorkUnit,Converter={StaticResource WorkUnitToEndTimeStringConverter}}"
FontSize="28"
FontFamily="Segoe WP Light"
Foreground="{StaticResource TsColorWhite}"/>
不幸的是,当模型中的 To 属性发生变化时,即使调用OnPropertyChanged(参见上面的模型代码),文本块也不会更新。我假设原因是只更新那些控件,其中某些属性直接绑定到更改的模型属性。
因为我需要来自WorkUnit的3个属性才能正确格式化 To 我更改了绑定,如下所示。我将 Text 绑定到 WorkUnit.To 并将 ConverterParameter 设置为WorkUnit本身。有了这个改变,我希望无论何时在模型中更改 To 并调用值转换器,我都可以格式化时间,因为我拥有转换器参数(WorkUnit)提供的所有信息。 (我不在这里打印更新的转换器,但我更改了它以适应值和参数输入参数的更改)
<TextBlock
Grid.Column="2" Grid.Row="0"
Grid.ColumnSpan="2"
Text="{Binding WorkUnit.To,Converter={StaticResource WorkUnitToEndTimeStringConverter},ConverterParameter={Binding WorkUnit}}"
FontSize="28"
FontFamily="Segoe WP Light"
Foreground="{StaticResource TsColorWhite}"/>
不幸的是,在这种情况下会抛出XamlParseException异常。
{System.Windows.Markup.XamlParseException: Failed to assign to property 'System.Windows.Data.Binding.ConverterParameter'. [Line: 61 Position: 18] ---> System.InvalidOperationException: Operation is not valid due to the current state of the object.
at MS.Internal.XamlManagedRuntimeRPInvokes.TryApplyMarkupExtensionValue(Object target, XamlPropertyToken propertyToken, Object value)
at MS.Internal.XamlManagedRuntimeRPInvokes.SetValue(XamlTypeToken inType, XamlQualifiedObject& inObj, XamlPropertyToken inProperty, XamlQualifiedObject& inValue)
--- End of inner exception stack trace ---
at System.Windows.Application.LoadComponent(Object component, Uri resourceLocator)}
那么有没有办法从我的模型中删除formatter-property,以便尽可能让我的模型保持干净?是吗?我的转换器有问题吗?还有其他方式,我目前不知道吗?
答案 0 :(得分:0)
您可以在WorkUnit
EndTimeString
public string EndTimeString
{
get
{
string endTime = null;
if (this.To.HasValue)
{
if (this.Type == WorkUnitType.StartEnd)
{
endTime = this.To.Value.ToString(CultureInfo.CurrentCulture.DateTimeFormat.ShortTimePattern);
}
else
{
var duration = this.To.Value - this.From;
endTime = DateHelper.FormatTime(duration, false);
}
}
return endTime
}
}
当然,如果To
值发生变化,您希望通知用户界面EndTimeString
也已更改,以便它获取新值:
public DateTime? To
{
get { return Get<DateTime?>(); }
set
{
Set(value);
OnPropertyChanged("To");
OnPropertyChanged("EndTimeString");
}
}
然后直接绑定到该字符串:
...Text="{Binding WorkUnit.EndTimeString}" />
答案 1 :(得分:0)
不幸的是你无法绑定参数。
MultiBinding
这很容易,但是Windows Phone无法使用(正如您在评论中所述)。
你可以自己实现它,但如果你真的没有实现它:),有些实现试图模仿这种行为。其中一个可以从the joy of code找到。
有一个名为Krempel's WP7 Library的NuGet包,其上面已经为WP7实现了,但它也适用于WP8.x。
缺点是它只能绑定到可视树中的元素,因此您必须使用(缺少更好的词)来转发UI元素以完成工作。当我无法直接绑定到我需要的属性时,我自己也使用了类似的技术。一个案例是AppBar
,你不能直接绑定到启用的属性,所以我使用类似
<CheckBox Grid.Row="0" IsEnabled="{Binding AppBarEnabled}"
IsEnabledChanged="ToggleAppBar" Visibility="Collapsed" />
无论如何,下面是完整的例子,没有任何groovy模式,关于如何使用上面的库实现多重绑定。尝试一下,看看是否值得麻烦。选项是您的模型中具有“额外”属性或视图中的一些额外元素和复杂性。
我使用了您的WorkUnit模型和转换器,使其更有用,更易于理解。
结果应该是这样的。
<强> MainWindow.xaml 强>
<phone:PhoneApplicationPage
x:Class="WP8MultiBinding.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:controls="clr-namespace:Krempel.WP7.Core.Controls;assembly=Krempel.WP7.Core"
xmlns:conv="clr-namespace:WP8MultiBinding"
mc:Ignorable="d"
FontFamily="{StaticResource PhoneFontFamilyNormal}"
FontSize="{StaticResource PhoneFontSizeNormal}"
Foreground="{StaticResource PhoneForegroundBrush}"
SupportedOrientations="Portrait" Orientation="Portrait"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
shell:SystemTray.IsVisible="True">
<phone:PhoneApplicationPage.Resources>
<conv:WorkUnitToEndTimeStringConverter
x:Key="WorkUnitToEndTimeStringConverter" />
</phone:PhoneApplicationPage.Resources>
<Grid x:Name="LayoutRoot">
<Grid.RowDefinitions>
<RowDefinition Height="80" />
<RowDefinition Height="80" />
<RowDefinition />
</Grid.RowDefinitions>
<!-- Multibinding & converter -->
<controls:MultiBinding
x:Name="MultiBinding"
Converter="{StaticResource WorkUnitToEndTimeStringConverter}"
NumberOfInputs="3"
Input1="{Binding ElementName=Type, Path=Text, Mode=TwoWay}"
Input2="{Binding ElementName=From, Path=Text, Mode=TwoWay}"
Input3="{Binding ElementName=To, Path=Text, Mode=TwoWay}" />
<!-- Output from multibinded conversion -->
<TextBox Text="{Binding ElementName=MultiBinding, Path=Output}" Grid.Row="0" />
<!-- Update WorkUnit properties -->
<Button Click="UpdateButtonClick" Grid.Row="1">Test MultiBinding</Button>
<!-- Helper elements, might want to set visibility to collapsed -->
<StackPanel HorizontalAlignment="Center" Grid.Row="2">
<TextBlock x:Name="Type" Text="{Binding WorkUnit.Type, Mode=TwoWay}" />
<TextBlock x:Name="From" Text="{Binding WorkUnit.From, Mode=TwoWay}" />
<TextBlock x:Name="To" Text="{Binding WorkUnit.To, Mode=TwoWay}" />
</StackPanel>
</Grid>
</phone:PhoneApplicationPage>
<强> MainWindow.xaml.cs 强>
using System;
using System.ComponentModel;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Windows;
using Microsoft.Phone.Controls;
namespace WP8MultiBinding
{
public partial class MainPage : PhoneApplicationPage
{
public MainPage()
{
InitializeComponent();
WorkUnit = new WorkUnit()
{
To = DateTime.Now.AddHours(5),
From = DateTime.Now,
Type = WorkUnitType.StartEnd
};
}
public WorkUnit WorkUnit { get; set; }
// Ensure bindings do update
private void UpdateButtonClick(object sender, RoutedEventArgs e)
{
WorkUnit.Type = WorkUnit.Type == WorkUnitType.StartEnd ?
WorkUnit.Type = WorkUnitType.Other :
WorkUnit.Type = WorkUnitType.StartEnd;
WorkUnit.From = WorkUnit.From.AddMinutes(60);
if (WorkUnit.To.HasValue)
WorkUnit.To = WorkUnit.To.Value.AddMinutes(30);
}
}
public enum WorkUnitType
{
StartEnd,
Other
}
public class WorkUnit : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private WorkUnitType _type;
private DateTime _from;
private DateTime? _to;
public WorkUnitType Type
{
get { return _type; }
set { _type = value; OnPropertyChanged(); }
}
public DateTime From
{
get { return _from; }
set { _from = value; OnPropertyChanged(); }
}
public DateTime? To
{
get { return _to; }
set { _to = value; OnPropertyChanged(); }
}
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
// Multivalue Converter
public class WorkUnitToEndTimeStringConverter : Krempel.WP7.Core.Controls.IMultiValueConverter
{
private const string DateFormat = "M/d/yyyy h:mm:ss tt";
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
// Index: 0 = Type, 1 = From, 2 = To
if (values[2] != null)
{
var type = (WorkUnitType) Enum.Parse(typeof (WorkUnitType), values[0].ToString());
var from = DateTime.ParseExact(values[1].ToString(), DateFormat, CultureInfo.InvariantCulture);
var to = DateTime.ParseExact(values[2].ToString(), DateFormat, CultureInfo.InvariantCulture);
if (type == WorkUnitType.StartEnd)
return to.ToString(CultureInfo.CurrentCulture.DateTimeFormat.ShortTimePattern);
var duration = to - from;
return duration; // DateHelper.FormatTime(duration, false);
}
return null;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}