WPF绑定依赖属性

5

我遇到了一个在UserControl中绑定依赖属性的问题。当它初始化时,它会得到一个值,但是之后它不会更新。 我可能错过了一些明显的东西,以下是一些代码片段:

这是我绑定BalanceContent依赖属性的地方:

<Game:PlayerDetails x:Name="SelectedPlayerDetails" Grid.Row="1" Grid.Column="2" Grid.ColumnSpan="2" Grid.RowSpan="4" 
                          BalanceContent="{Binding Source={StaticResource UserData}, Path=SelectedUser.Balance, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}">

    </Game:PlayerDetails>

这是在UserControl中的TextBox

 <TextBox  VerticalAlignment="Center" FontFamily="Formata" FontSize="20" Grid.Column="2" 
         Text="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type UserControl}}, Path=BalanceContent}" 
             Grid.Row="7"></TextBox>

以下是依赖属性:

public static readonly DependencyProperty BalanceContentProperty = DependencyProperty.Register(
        "BalanceContent", typeof(string), typeof(PlayerDetails));

    public string BalanceContent
    {
        get
        {return (string) GetValue(BalanceContentProperty);}
        set
        {SetValue(BalanceContentProperty, value);}
    }

这是一个使用UserControl的视图,其中包含被更新的选定用户列表:

<ListView x:Name="lstAccounts"  Grid.Column="0" Grid.Row="2" Grid.ColumnSpan="2" Grid.RowSpan="4" 
              ItemsSource="{Binding Source={StaticResource UserData}, Path=CurrentUserSearch}" 
              SelectedItem="{Binding Source={StaticResource UserData}, Path=SelectedUser}"

SelectedUser 是在实现了 INotifyPropertyChanged 接口的类中定义的:

 public User SelectedUser
    {
        get
        {
            return _selectedUser;
        } 
        set
        {
            _selectedUser = value;
            OnPropertyChanged(new PropertyChangedEventArgs("SelectedUser"));
        }
    }

这个想法是,当在列表中选择一个新的用户时,TextBox应该更新,但目前它没有更新。我已经将绑定放在本地的TextBox上,并且可以正常更新,但不能在DependencyProperty上更新。感谢任何帮助。

5个回答

4

以下是您可以尝试的一些可能性:

首先,您的ListView可能没有更新ViewModel的SelectedUser属性。尝试将ListView中的绑定设置为“TwoWay”模式:

<ListView x:Name="lstAccounts"  Grid.Column="0" Grid.Row="2" Grid.ColumnSpan="2" Grid.RowSpan="4" 
          ItemsSource="{Binding Source={StaticResource UserData}, Path=CurrentUserSearch}" 
          SelectedItem="{Binding Source={StaticResource UserData}, Path=SelectedUser, Mode=TwoWay}"/>

您可以更好地组织DataContext的定义方式。请记住,您UserControl的所有子控件都可以访问它的DataContext而无需使用相对绑定(它们会继承它)。由于您的PlayerInfo控件依赖于SelectedUser,请将其DataContext设置为SelectedUser,可以将其绑定到ListView的SelectedUser或UserData视图模型中的SelectedUser。

 <Game:PlayerDetails x:Name="SelectedPlayerDetails" Grid.Row="1" Grid.Column="2" Grid.ColumnSpan="2" Grid.RowSpan="4" DataContext="{Binding Source={StaticResource UserData}, Path=SelectedUser}"
                      BalanceContent="{Binding Balance, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}">

</Game:PlayerDetails>

当前所选用户的源头也可以是 ListView:

<Game:PlayerDetails x:Name="SelectedPlayerDetails" Grid.Row="1" Grid.Column="2" Grid.ColumnSpan="2" Grid.RowSpan="4" DataContext="{Binding SelectedItem, ElementName=lstAccounts}"
                      BalanceContent="{Binding Balance, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}">

</Game:PlayerDetails>

无论哪种方式,您将能够在TextBox上执行以下操作,因为其DataContext将与其父控件的DataContext相同:
<TextBox  VerticalAlignment="Center" FontFamily="Formata" FontSize="20" Grid.Column="2" 
     Text="{Binding Balance}" 
         Grid.Row="7"></TextBox>

如果用户控件依赖于根视图模型来实现命令和其他高级逻辑,那么请将DataContext设置为它,以便您可以轻松访问SelectedUser。
<Game:PlayerDetails x:Name="SelectedPlayerDetails" Grid.Row="1" Grid.Column="2" Grid.ColumnSpan="2" Grid.RowSpan="4" DataContext="{StaticResource UserData}"
                      BalanceContent="{Binding SelectedUser.Balance, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}">

</Game:PlayerDetails>

所以你可以这样做:
<TextBox  VerticalAlignment="Center" FontFamily="Formata" FontSize="20" Grid.Column="2" 
     Text="{Binding SelectedUser.Balance}" 
         Grid.Row="7"></TextBox>

在这个第二种方法中,您需要检查一件我不确定的事情。我知道当控件的DataContext更改时,它会正确地更新所有相关绑定。例如,如果您将PlayerDetails的DataContext更改为另一个UserData实例,那么BalanceContent属性也将更新。但是,在这种情况下,BalanceContent取决于UserData的SelectedUser属性。因此,它将侦听该User实例的属性更改。如果SelectedUser.Balance发生更改(并且User实现了INotifyPropertyChanged,或者它是一个DependencyProperty),则BalanceContent将更新。现在,如果UserData中的SelectedUser实例更改,我不确定BalanceContent是否会更新,因为我认为绑定不会侦听其路径中每个对象的更改。
编辑
最后一点可能是我在使用xaml开发时遇到的第一个问题。我在Silverlight中有一个DataGrid,其实体类型具有复杂类型的属性。其中一列依赖于复杂类型的属性。如果我更改复杂类型的值,则该列将更新(它实现了INPC)。如果我更改实体的复杂类型实例,则该列将不会更新...解决方案是级联DataContexts:我创建了一个模板列,将列的绑定设置为复杂类型,而不是它的属性。然后我将模板中TextBox的文本绑定到复杂类型的属性上,因为现在它是TextBox的DataContext。
在您的情况下,您可以为TextBox.Text这样做,但不能为PlayerDetails.BalanceContent这样做。您可以将TextBox的DataContext绑定到UserData的SelectedUser,然后将Text绑定到Balance属性。
<TextBox  VerticalAlignment="Center" DataContext="{Binding SelectedUser}" FontFamily="Formata" FontSize="20" Grid.Column="2" 
 Text="{Binding Balance}" 
     Grid.Row="7"></TextBox>

感谢回复。在我详细阅读整篇文章之前,我想快速评论一下。ViewModel 中的 selecteduser 已经得到更新,因为这是我最先考虑的事情之一。我还有一个 TextBox 是窗口的成员,使用与依赖属性相同的绑定可以很好地更新余额。该值被正确初始化,但在后续更新中,控件的 TextBox 中的值仍然保持不变,因此我认为您最后的观点需要进一步调查。 - paj7777
所以将SelectedUser的源更改为ListView确实起作用,尽管XAML抱怨无法解析Balance,我想在设计时它显然会这样做。 - paj7777

1
请尝试更改您用户控件内文本框的绑定,将其绑定到BalanceContent,这是用户控件依赖属性(您原始的绑定源似乎是数据上下文属性),然后进行测试。
Text="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type UserControl}}, Path=BalanceContent}"

编辑:请尝试以下代码

public User SelectedUser
    {
        get
        {
            return _selectedUser;
        } 
        set
        {
            _selectedUser = value;
            OnPropertyChanged(new PropertyChangedEventArgs("SelectedUser"));
            OnPropertyChanged(new PropertyChangedEventArgs("SelectedUserBalance"));
        }
    }

public string SelectedUserBalance
    {
        get
        {
            return _selectedUser.Balance;
        } 
    }

<Game:PlayerDetails x:Name="SelectedPlayerDetails" Grid.Row="1" Grid.Column="2" Grid.ColumnSpan="2" Grid.RowSpan="4" 
                          BalanceContent="{Binding Source={StaticResource UserData}, Path=SelectedUserBalance, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}">

</Game:PlayerDetails>

<TextBox  VerticalAlignment="Center" FontFamily="Formata" FontSize="20" Grid.Column="2" 
         Text="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type UserControl}}, Path=BalanceContent}"
             Grid.Row="7"></TextBox>

谢谢尝试,但它的行为方式仍然相同。 - paj7777
我已经编辑过我的代码以反映你的建议,至少源代码更明显了。然而,它仍然像以前一样工作,在初始化时更新值,但不会在值改变时更新。 - paj7777
是的,更新绑定的原因是原始绑定源绑定到DataContext上的属性,但似乎它是您自定义控件的依赖属性。请问您能否发布您的依赖属性代码? - byte
请在您的ViewModel上创建另一个属性,我们称之为SelectedUserBalance,并将其用作控件的BalanceContent属性的源。同时,在每次设置SelectedUser时,请更新SelectedUser以包括OnPropertyChanged(new PropertyChangedEventArgs("SelectedUserBalance"))。 - byte
好的,我已经添加了依赖属性的代码。添加SelectedUserBalance属性没有起作用。 - paj7777

0
<TextBox VerticalAlignment="Center" FontFamily="Formata" FontSize="20" Grid.Column="2" 
       Text="{Binding SelectedItem.BalanceContent, ElementName=lstAccounts}" Grid.Row="7"></TextBox>

忘记UserControl的绑定,直接将TextBox绑定到SelectedItem。我希望这能有所帮助。


抱歉,我觉得我没有表达清楚。lstAccounts是一个视图,它使用了包含TextBox的UserControl。 - paj7777

0

您正在将StaticResource绑定到属性。与DynamicResources相反,StaticResources仅在窗口(或UserControl)初始化时加载一次。DynamicResources仅在需要时加载(并且每次需要时都加载)。对DynamicResource进行的任何更改都会立即被捕获,并相应地更新属性。只需将关键字“StaticResource”替换为“DynamicResource”,属性绑定应该在每次资源更改时更新。

注意:如果StaticResource是从Freezable类派生的对象,则它也会表现得有点像DynamicResource。例如,如果您的资源是Brush,则绑定的属性将在每次以任何方式更改Brush对象时更新(例如通过更改其不透明度),但这是由于从Freezable类继承的行为。但是,如果您用新的Brush对象替换StaticResource中的Brush对象,则不会捕获此更改,因为更改已经针对静态的Resource而非Brush进行了更改。

还要注意:即使是仅作为StaticResources可用的资源,例如数据SystemColors、SystemFonts(提供对系统设置的访问),也可以包装在DynamicResource中,以确保在每次更改时更新属性。可以通过以下方式完成:

<Control Property="{DynamicResource {x:Static SystemColors.XXX}}"></Control>


这段代码与编程有关。

0
文本框正在尝试绑定到用户控件的DataContext。给用户控件定义一个x:Name,并将TextBox的DataContext设置为"DataContext="{Binding ElementName=[YourControlName]}"。

感谢您的输入,但即使进行了更改,它仍然表现得一样。 - paj7777

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接