繁体   English   中英

WP8.1 Silverlight中将对象绑定到ConverterParameter失败,并出现XamlParseException

[英]Bind object to ConverterParameter fails with XamlParseException in WP8.1 Silverlight

初始情况

我有一个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属性给定的时间,具体取决于TypeFrom属性,格式为字符串。

为了清理我的模型,我想尽可能地删除此类formatter-properties,并尽可能使用转换器(IValueConverter)。 离开此类属性的另一个原因是,我使用的数据库(iBoxDB)没有像[Ignore]这样的成员属性可用于SQLite。 因此,所有具有受支持类型的属性都存储在数据库中。 但是,如果可能的话,不应存储此类格式化程序属性。

我做了什么-第一次尝试

现在,我将所有属性都转换为转换器,并且在大多数情况下,这没有问题。 但是, 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();
    }
}

因此,我将显示格式化的To属性的TextBlock中的Text属性的绑定更改为绑定到页面的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个属性改变了绑定,如下所示。 我将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)}

那么有没有办法从我的模型中删除格式化程序属性,以便使我的模型尽可能保持清洁? 有……吗? 我的转换器错了吗? 我目前不知道还有其他方法吗?

您可以在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值更改,您想通知UI EndTimeString也已更改,以便它获取新值:

public DateTime? To
{
    get { return Get<DateTime?>(); }
    set
    {
        Set(value);
        OnPropertyChanged("To");
        OnPropertyChanged("EndTimeString");
    }
}

然后直接绑定到该字符串:

...Text="{Binding WorkUnit.EndTimeString}" />

不幸的是,您无法绑定到参数。

使用MultiBinding可以很容易,但是Windows Phone无法使用它(如您在评论中所述)。

您可以自己实现它,但是如果您不是真的实现它:),则有一些实现试图模仿这种行为。 代码的乐趣中可以找到其中之一。

有一个名为Krempel的WP7库的NuGet程序包,上面已为WP7实现了该程序包,但它也适用于WP8.x。

缺点是它只能绑定到可视树中的元素,因此您必须使用(由于缺少更好的用词)中继UI元素才能完成工作。 当我无法直接绑定到需要的属性时,我自己也使用了类似的技术。 一种情况是AppBar ,您不能直接绑定到enabled属性,所以我使用类似

<CheckBox Grid.Row="0" IsEnabled="{Binding AppBarEnabled}" 
          IsEnabledChanged="ToggleAppBar" Visibility="Collapsed" />

无论如何,下面是一个完整的示例,没有任何常规模式,说明如何使用上面的库实现多重绑定。 尝试一下,看看是否值得这样做。 选项是您在模型中具有“额外”属性,或者在视图中具有一些额外的元素和复杂性。

我使用您的WorkUnit模型和Converter使它更有用,更易于理解。

结果应该是这样的。

在此处输入图片说明


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();
        }
    }

}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM