简体   繁体   English

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

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

Initial situation 初始情况

I have a Windows Phone 8.1 Silverlight app where I have a model that contains several properties as shown below (just an excerpt of 3 properties, it has a lot more). 我有一个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");
        }
    }
}

I'm using MVVMLight. 我正在使用MVVMLight。 There are several work units in an ObservableCollection that is bound to a list box on a Windows Phone page. ObservableCollection中有多个工作单元,这些工作单元绑定到Windows Phone页面上的列表框。 The collection itself is part of a (WorkDay) view model which in turn is bound to the page itself. 集合本身是(WorkDay)视图模型的一部分,该视图模型又绑定到页面本身。

What I want to do 我想做的事

I have a lot of properties in my model that are just used to format some properties for the UI. 我的模型中有很多属性,这些属性仅用于格式化UI的某些属性。 One such is ToAsShortTimeString which returns the time given by the To property, depending on the Type and the From properties, formatted as string. ToAsShortTimeString就是这样的一种,它返回To属性给定的时间,具体取决于TypeFrom属性,格式为字符串。

In order to clean up my model I want to remove such formatter-properties as much as possible and use converters (IValueConverter) as much as possible. 为了清理我的模型,我想尽可能地删除此类formatter-properties,并尽可能使用转换器(IValueConverter)。 One further reason to move away from such properties is that the database that I use (iBoxDB) doesn't have member attributes like [Ignore] that is available for SQLite. 离开此类属性的另一个原因是,我使用的数据库(iBoxDB)没有像[Ignore]这样的成员属性可用于SQLite。 So all properties with supported types are stored in the database. 因此,所有具有受支持类型的属性都存储在数据库中。 However, such formatter properties shouldn't be stored if possible. 但是,如果可能的话,不应存储此类格式化程序属性。

What I did - 1st try 我做了什么-第一次尝试

I now transformed all properties to converters and most of the time this was no problem. 现在,我将所有属性都转换为转换器,并且在大多数情况下,这没有问题。 However, ToAsShortTimeString not just uses one property but 3 to format the input. 但是, ToAsShortTimeString不仅使用一个属性,而且使用3属性来格式化输入。 Therefore, in XAML I need to provide either those 3 properties to the value converter or the work unit itself which is bound to the page. 因此,在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();
    }
}

So I changed the binding of the Text property in the TextBlock that shows the formatted To property to the WorkUnit that is bound to the page. 因此,我将显示格式化的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}"/>

Unfortunately, when the To property changes in the model, even though OnPropertyChanged is called (see model code above), the text block doesn't get updated. 不幸的是,当To属性在模型中更改时,即使调用了OnPropertyChanged(请参见上面的模型代码),文本块也不会更新。 I assume the reason is that only those controls are updated where some property is directly bound to the changed model property. 我认为原因是只有那些属性直接绑定到更改后的模型属性的控件才被更新。

What I did - 2nd try 我做了什么-第二次尝试

So as I need 3 properties from WorkUnit in order to correctly format To I changed the binding as follows. 因此,正如我需要以正确的格式, 我从WorkUnit的3个属性改变了绑定,如下所示。 I bound Text to WorkUnit.To and set the ConverterParameter to the WorkUnit itself. 我将Text绑定到WorkUnit.To并将ConverterParameter设置为WorkUnit本身。 With this change I hoped that whenever To is changed in the model and the value converter is called, I can format the time because I have all the info provided from the converter parameter (WorkUnit). 希望通过此更改,只要在模型中更改To并调用值转换器时,就可以格式化时间,因为我拥有转换器参数(WorkUnit)提供的所有信息。 (I'm not printing the updated converter here but I changed it to accomodate the change on the value and parameter input parameters) (我不是在这里打印更新的转换器,而是为了适应值和参数输入参数的更改而对其进行了更改)

<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}"/>

Unfortunately, in this case a XamlParseException exception is thrown. 不幸的是,在这种情况下,将抛出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)}

Question

So is there a way to remove the formatter-property from my model so that I can keep my model as clean as possible? 那么有没有办法从我的模型中删除格式化程序属性,以便使我的模型尽可能保持清洁? Is there sth. 有……吗? wrong with my converter? 我的转换器错了吗? Is there any other way that I currently don't know of? 我目前不知道还有其他方法吗?

You could have a property in your WorkUnit called EndTimeString 您可以在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
    }
}

Of course, if To value changes, you want to notify the UI that the EndTimeString has also changed so that it picks up the new value: 当然,如果To值更改,您想通知UI EndTimeString也已更改,以便它获取新值:

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

And then just bind straight to that string: 然后直接绑定到该字符串:

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

Unfortunately you can't bind to parameters. 不幸的是,您无法绑定到参数。

This would be easy with MultiBinding but that's not available for Windows Phone (as you stated in your comment). 使用MultiBinding可以很容易,但是Windows Phone无法使用它(如您在评论中所述)。

You could implement it yourself but if you are not really into it :), there are implementations trying to mimic this behaviour. 您可以自己实现它,但是如果您不是真的实现它:),则有一些实现试图模仿这种行为。 One of them can be found from the joy of code . 代码的乐趣中可以找到其中之一。

There is a NuGet package called Krempel's WP7 Library which has above implemented for WP7 but it works on WP8.x as well. 有一个名为Krempel的WP7库的NuGet程序包,上面已为WP7实现了该程序包,但它也适用于WP8.x。

Downside is that it can only bind to elements in visual tree, so you have to use (for lack of a better word) relaying UI elements to get the job done. 缺点是它只能绑定到可视树中的元素,因此您必须使用(由于缺少更好的用词)中继UI元素才能完成工作。 I have used similar technique myself when I can't bind directly to a property I need to. 当我无法直接绑定到需要的属性时,我自己也使用了类似的技术。 One case is AppBar , you can't bind directly to enabled property, so instead I use something like 一种情况是AppBar ,您不能直接绑定到enabled属性,所以我使用类似

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

Anyway, below is full example, without any groovy patterns, on how you can achieve multibinding using above library. 无论如何,下面是一个完整的示例,没有任何常规模式,说明如何使用上面的库实现多重绑定。 Try it out and see if it's worth the trouble. 尝试一下,看看是否值得这样做。 Options are that you have "extra" properties in your model or some extra elements and complexity in your view. 选项是您在模型中具有“额外”属性,或者在视图中具有一些额外的元素和复杂性。

I used your WorkUnit model and Converter to make it more useful and easier to understand. 我使用您的WorkUnit模型和Converter使它更有用,更易于理解。

Outcome should be something like this. 结果应该是这样的。

在此处输入图片说明


MainWindow.xaml 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 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