首页 > 解决方案 > WPF MVVM:如何在用户控件上设置绑定?

问题描述

我在掌握绑定到用户控件的工作原理以及为什么它似乎与页面上的工作方式不同时遇到了问题。我要做的是创建一个错误显示(错误名称、描述和提示,告诉如何解决它),如果有错误,它将从内容控件中显示,如果没有错误,则显示其他内容。

我正在使用用户控件执行此操作,该控件本质上是页面上的子视图,以避免出现粗鲁的弹出窗口,并将在多个页面上重复使用。我有内容控件绑定工作,所以我们显示了用户控件,只是没有信息。

出于“干”的目的,我创建了一个具有所需属性的错误模型,然后使用一个类将该模型实现为错误列表。在构造函数中,我只是将新错误添加到列表中......这样所有应用程序的错误都在同一个位置以便于维护。

系统错误类:

public List<ErrorMessageModel> errors;

/// <summary>
/// Constructor creates list with all errors in the program
/// </summary>
public SystemErrors()
{
            
    errors = new List<ErrorMessageModel>()
    {
        //*** No Error ***/
        new ErrorMessageModel(ErrorCodes.noError, "", "", ""),

        /*** No Devices Found Error ***/
        new ErrorMessageModel(ErrorCodes.noDevicesConnected,
                              "No Devices Found",
                              "We couldn't find any attached USB devices.",
                              "This error occurs when there's no connection between the device and the computer ")

        /*** Next Error ***/
    };
}

private ErrorMessageModel _activeError;
public ErrorMessageModel ActiveError
{
    get { return _activeError; }
    set
    {
        if (value == _activeError)
            return;

        _activeError = value;
        RaisePropertyChanged();
    }
}

public void SetActiveError (byte index)
{
    // Changed to ActiveError = after Mark's answer. No effect.
    _activeError = errors[index];

}

在页面的视图模型中,我们使用枚举 ErrorCodes 来命名指向错误索引的名称。因此,当我们遇到错误时,我们将 errorCode 传递给将其转换为字节的方法,然后调用 SetActiveError (byte errorCodeToIndex)。

页面视图模型:

...
private void parseErrorCode(ErrorCodes error)
{
    // Convert Error Code into Index number
    var errorCodeToIndex = (byte)error;

    // Create new error list and populate list
    SystemErrors errors = new SystemErrors();

    errors.SetActiveError(errorCodeToIndex);
}

现在这里的想法是将用户控件的数据上下文设置为 SystemError,从而绑定到 ActiveError(ActiveError.ErrorName、ActiveError.ErrorDescription 等)。我的想法是这将允许我们使用单个数据上下文,因为无论出现错误时我们在哪个页面上,错误信息总是来自 SystemErrors。

用户控制:

<UserControl x:Class="FirmwareUpdaterUI.Views.ConnectionErrorView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:FirmwareUpdaterUI.Views"
             xmlns:vm="clr-namespace:FirmwareUpdaterUI.ViewModels"
             xmlns:e="clr-namespace:FirmwareUpdaterUI.Errors"
             mc:Ignorable="d" 
             d:DesignHeight="250" d:DesignWidth="400" BorderBrush="Red" BorderThickness="1px">

    <UserControl.DataContext>
        <e:SystemErrors/>
    </UserControl.DataContext>

    <Grid x:Name="ConnectionErrorView" Visibility="Visible">
            <Grid.RowDefinitions>
                <RowDefinition Height=".5*"/>
                <RowDefinition Height="auto"/>
                <RowDefinition Height="auto"/>
                <RowDefinition Height="auto"/>
                <RowDefinition Height="6*"/>
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="1.5*"/>
                <ColumnDefinition Width=".5*"/>
                <ColumnDefinition Width="10*"/>
                <ColumnDefinition Width="1*"/>
            </Grid.ColumnDefinitions>

            <!-- Row 1-->
            <StackPanel Grid.Row="1" Grid.Column="2" Orientation="Horizontal">
                <TextBlock>
                    Error:
                </TextBlock>
                <TextBlock Text="{Binding ActiveError.ErrorName, 
                           RelativeSource={RelativeSource AncestorType={x:Type e:SystemErrors}}}"/>
            </StackPanel>

            <!-- Row 2 -->
            <TextBlock Grid.Row="2" Grid.Column="2" Grid.ColumnSpan="2"
                       Text="{Binding ErrorDescription}"/>

            <!-- Row 3 -->
            <TextBlock Grid.Row="3" Grid.Column="2" Grid.RowSpan="2" Grid.ColumnSpan="2" 
                       Text="{Binding Path=ActiveError.ErrorTips, StringFormat=Tips: {0}}" />
        </Grid>

</UserControl>

但是,我似乎无法让它工作。您可以在 XAML 中看到我所有剩余的失败方法,但这只是我尝试过的皮毛。如果我将 UC 的内容切掉并将其粘贴到页面中,我可以让它工作,所以这告诉我绑定到页面的机制与绑定到用户控件的机制不同。

我已经阅读了很多教程,观看了一些视频,但所有这些都略过了它的工作原理;它总是“为了使这项工作,我们需要这个已经工作的代码”,这只有在您遇到完全相同的问题时才有帮助。我已经看到了依赖属性,似乎是正常绑定,相对源到自身,相对源到祖先等。

问题:

那么为什么用户控件似乎具有与窗口/页面不同的绑定机制(为什么数据上下文不像其他地方那样工作)?如果我们需要依赖属性,那么为什么我们不需要它们来绑定到页面呢?如果需要的话,关于 DPs,在这种情况下,我是否只需要创建一个 ErrorModel 类型的 ActiveErrorProperty,或者我们是否需要为每个子属性(字符串类型的 ErrorName)创建一个?我们如何将 DP 链接到我们想要绑定的属性?

更新:

今天一整天都在尝试让这个工作,所以我开始跟踪并输出到控制台。两者都没有绑定错误,如果我坚持afterTrace.WriteLine的公共声明,将被设置为正确的错误。然后我尝试跟踪 XAML 中的绑定,并且有一些有趣的事情:ActiveErrorRaisePC()ActiveError

ErrorName(_activeError)= No Devices Found
ErrorName(ActiveError)= No Devices Found
System.Windows.Data Warning: 56 : Created BindingExpression (hash=62991470) for Binding (hash=23560597)
System.Windows.Data Warning: 58 :  Path: 'ActiveError.ErrorName'
System.Windows.Data Warning: 60 : BindingExpression (hash=62991470): Default mode resolved to OneWay
System.Windows.Data Warning: 62 : BindingExpression (hash=62991470): Attach to System.Windows.Controls.TextBlock.Text (hash=2617844)
System.Windows.Data Warning: 67 : BindingExpression (hash=62991470): Resolving source 
System.Windows.Data Warning: 70 : BindingExpression (hash=62991470): Found data context element: TextBlock (hash=2617844) (OK)
System.Windows.Data Warning: 78 : BindingExpression (hash=62991470): Activate with root item SystemErrors (hash=52209455)
System.Windows.Data Warning: 108 : BindingExpression (hash=62991470):   At level 0 - for SystemErrors.ActiveError found accessor RuntimePropertyInfo(ActiveError)
System.Windows.Data Warning: 104 : BindingExpression (hash=62991470): Replace item at level 0 with SystemErrors (hash=52209455), using accessor RuntimePropertyInfo(ActiveError)
System.Windows.Data Warning: 101 : BindingExpression (hash=62991470): GetValue at level 0 from SystemErrors (hash=52209455) using RuntimePropertyInfo(ActiveError): <null>
System.Windows.Data Warning: 106 : BindingExpression (hash=62991470):   Item at level 1 is null - no accessor
System.Windows.Data Warning: 80 : BindingExpression (hash=62991470): TransferValue - got raw value {DependencyProperty.UnsetValue}
System.Windows.Data Warning: 88 : BindingExpression (hash=62991470): TransferValue - using fallback/default value ''
System.Windows.Data Warning: 89 : BindingExpression (hash=62991470): TransferValue - using final value ''

请注意,在我们看到绑定失败之前,它显示ActiveError设置正确(前两行,“No Devices Found”是错误名称) 。我对 WPF 太陌生了,但是如果我正确地解释了跟踪,它看起来就像在 datacontext 中找到但无法从中获取任何东西,我们知道它被设置为正确的值。那是怎么回事?ActiveErrorSystemErrorsActiveError.ErrorName

标签: wpfxamlmvvmdata-bindinguser-controls

解决方案


SystemErrors不是视觉的祖先UserControlDataContext就绑定而言,如果ErrorMessageModel该类具有ErrorName返回您期望它返回的内容的公共属性,则以下内容应该可以工作:

<TextBlock Text="{Binding ActiveError.ErrorName}"/>

但是,以下内容不会设置ErrorMessageModel属性并引发PropertyChanged事件:

_activeError = errors[index];

您应该将该属性设置为一个新ErrorMessageModel对象:

public void SetActiveError(byte index)
{
    ActiveError = errors[index];
}

还要确保在 XAML 标记中创建SetActiveError的类的实际实例上调用该方法:SystemErrors

<UserControl.DataContext>
    <e:SystemErrors/>
</UserControl.DataContext>

推荐阅读