【问题标题】:Binding in WPF not working as expectedWPF 中的绑定无法按预期工作
【发布时间】:2014-01-06 20:24:20
【问题描述】:

我有一个 MainWindow.xaml、MainwindowViewModel.cs、HaemogramReport.xaml 和 HaemogramReport.xaml.cs。我的项目中还有其他文件,但问题在于上述四个文件。 我在这里发布最小代码,以便其他人可以发现问题。

现在在 HaemogramReport.xaml 中,我声明了一些控件,例如 GridTextBoxTextBlockRectangleBorderContentControl 等。

例如HaemogramReport.xaml 看起来像:

<Page.DataContext>
    <vm:MainWindowViewModel />
</Page.DataContext>

<Grid DataContext="{Binding Source={StaticResource Settings}}" PreviewMouseDown="Object_Selection" x:Name="Root">

    <Border Style="{StaticResource BorderStyle}" x:Name="HaemogramTestBorder"
            Grid.Row="{Binding Default.HaemogramTestGridRow}" Grid.Column="{Binding Default.HaemogramTestGridColumn}"
            Grid.RowSpan="{Binding Default.HaemogramTestGridRowSpan}" Grid.ColumnSpan="{Binding Default.HaemogramTestGridColumnSpan}">
        <Grid>
            <Rectangle Fill="Transparent" x:Name="HaemogramTestRectangle"/>
            <TextBlock x:Name="HaemogramTestTextBlock"
                       Text="{Binding Default.HaemogramTestText}" Visibility="{Binding Default.HaemogramTestVisibility}"
                       Background="{Binding Default.HaemogramTestBackground, Converter={StaticResource colorToSolidColorBrushConverter}}" 
                       Foreground="{Binding Default.HaemogramTestForeground, Converter={StaticResource colorToSolidColorBrushConverter}}"
                       FontFamily="{Binding Default.HaemogramTestFontFamily, Converter={StaticResource stringToFontFamilyConverter}}"
                       FontSize="{Binding Default.HaemogramTestFontSize}" 
                       FontWeight="{Binding Default.HaemogramTestFontWeight}" FontStyle="{Binding Default.HaemogramTestFontStyle}"
                       HorizontalAlignment="{Binding Default.HaemogramTestHorizontalAlignment}" 
                       VerticalAlignment="{Binding Default.HaemogramTestVerticalAlignment}"
                       Margin="{Binding Default.HaemogramTestMargin}" />
        </Grid>
    </Border>

</Grid>

当我点击上述声明元素中的任何元素时,会引发名为 Root 的网格的 mousedown 事件。

该事件处理程序位于HaemogramReport.xmal.cs。这里是:

private void Object_Selection(object sender, MouseButtonEventArgs e)
{
    var mouseWasDownOn = e.Source as FrameworkElement;

    if (mouseWasDownOn != null)
    {

        foreach (Border border in FindVisualChildren<Border>(Root))
        {
           border.BorderBrush = Brushes.Transparent;
        }

        if (!(mouseWasDownOn is Border))
        {
            FindParent<Border>(mouseWasDownOn).BorderBrush = Brushes.Orange;
        }

        MainWindowViewModel mwvm = new MainWindowViewModel();
        mwvm.SelectedObj = mouseWasDownOn;

    }
}

public static IEnumerable<T> FindVisualChildren<T>(DependencyObject depObj) where T : DependencyObject
{
    if (depObj != null)
    {
        for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
        {
            DependencyObject child = VisualTreeHelper.GetChild(depObj, i);
            if (child != null && child is T)
            {
                yield return (T)child;
            }

            foreach (T childOfChild in FindVisualChildren<T>(child))
            {
                yield return childOfChild;
            }
        }
    }
}

public static T FindParent<T>(DependencyObject child) where T : DependencyObject
{
    //get parent item
    DependencyObject parentObject = VisualTreeHelper.GetParent(child);

    //we've reached the end of the tree
    if (parentObject == null) return null;

    //check if the parent matches the type we're looking for
    T parent = parentObject as T;
    if (parent != null)
        return parent;
    else
        return FindParent<T>(parentObject);
}

在名为Root 的Grid 的mouseDown 处理程序中,我说mwvm.SelectedObj = mouseWasDownOn;

SelectedObj 是一个 FrameworkElement 类型的属性,在MainwindowViewModel.cs 中声明如下:

private FrameworkElement selectedObj;
public FrameworkElement SelectedObj
{
    get
    {
        return selectedObj;
    }
    set
    {
        selectedObj = value;
        OnPropertyChanged("SelectedObj");
    }
}

现在在我的 MainWindow 中,我有一个网格和一个文本框。有问题的绑定在这里声明。 xml 看起来像:

<Window.DataContext>
    <vm:MainWindowViewModel />
</Window.DataContext>

<Grid DataContext="{Binding SelectedObj, UpdateSourceTrigger=PropertyChanged}">
    <TextBox Text="{Binding Text, UpdateSourceTrigger=PropertyChanged, TargetNullValue='null', FallbackValue='Error'}"/>
</Grid>

使用上面的代码时,我总是在上面的 TextBox 中得到 Text Error

一开始我以为这可能是绑定错误,所以我将我的MainWindowViewModel.cs更改如下:

public class MainWindowViewModel : INotifyPropertyChanged
{
    public MainWindowViewModel()
    {
        SelectedObj = txt;
    }

    TextBlock txt = new TextBlock()
    {
        Text = "123"
    };

    private FrameworkElement selectedObj;
    public FrameworkElement SelectedObj
    {
        get
        {
            return selectedObj;
        }
        set
        {
            selectedObj = value;
            OnPropertyChanged("SelectedObj");
        }
    }
}

在运行项目时进行上述更改后,我可以在文本框中看到123,但是当我单击任何元素时,文本框中的文本不会改变。

现在的问题是,如果它是一个绑定错误,那么为什么在第二个示例中我在文本框中得到123 而在第一个示例中我得到Error - 后备值。

如果不是绑定错误,那么上面的代码有什么问题?

更新

调试时,我发现SelectedObjget 部分从未被调用过。但是不知道为什么?

更新 -- Reed Copsey

这是我的新课程:

public class DesignMethods
{

    public static void FindCurrentlyClickedElement(DependencyObject Root, MouseButtonEventArgs e, MainWindowViewModel vm)
    {
        var mouseWasDownOn = e.OriginalSource as FrameworkElement;

        if (mouseWasDownOn != null)
        {

            foreach (Border border in FindVisualChildren<Border>(Root))
            {
                border.BorderBrush = Brushes.Transparent;
            }

            if (!(mouseWasDownOn is Border))
            {
                FindParent<Border>(mouseWasDownOn).BorderBrush = Brushes.Orange;
            }

            vm.SelectedObj = mouseWasDownOn;

        }
    }

    public static IEnumerable<T> FindVisualChildren<T>(DependencyObject depObj) where T : DependencyObject
    {
        if (depObj != null)
        {
            for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
            {
                DependencyObject child = VisualTreeHelper.GetChild(depObj, i);
                if (child != null && child is T)
                {
                    yield return (T)child;
                }

                foreach (T childOfChild in FindVisualChildren<T>(child))
                {
                    yield return childOfChild;
                }
            }
        }
    }

    public static T FindParent<T>(DependencyObject child) where T : DependencyObject
    {
        //get parent item
        DependencyObject parentObject = VisualTreeHelper.GetParent(child);

        //we've reached the end of the tree
        if (parentObject == null) return null;

        //check if the parent matches the type we're looking for
        T parent = parentObject as T;
        if (parent != null)
            return parent;
        else
            return FindParent<T>(parentObject);
    }

}

我会这样使用它:

private void Object_Selection(object sender, MouseButtonEventArgs e)
{
    DesignMethods.FindCurrentlyClickedElement(Root, e, this.DataContext as MainWindowViewModel);
}

【问题讨论】:

    标签: c# wpf silverlight xaml


    【解决方案1】:

    问题是您正在创建 ViewModel 的新实例,而不是使用现有实例:

    // This is not the same instance you're binding to!
    // MainWindowViewModel mwvm = new MainWindowViewModel();
    
    // Get the existing one instead
    var mwvm = this.DataContext as MainWindowViewModel;
    mwvm.SelectedObj = mouseWasDownOn;
    

    请注意,我可能不会在这里使用“ViewModel”一词。您所做的并不是典型的 MVVM 场景,因为您将 DataContext 实例紧密耦合到 View 中,耦合发生在两个方向上,这与 MVVM 的正常目标几乎相反。


    编辑:

    您可能还需要更新SelectedObj 的绑定。我建议尝试将 XAML 设置为:

    <Grid>
        <TextBox Text="{Binding SelectedObj.Text, UpdateSourceTrigger=PropertyChanged, TargetNullValue='null', FallbackValue='Error'}"/>
    </Grid>
    

    【讨论】:

    • 我两天前问过这个问题。现在我把Object_Selection 搬到了一个新的班级,这样我就可以在很多地方使用它了。我也将此方法标记为static。而且我不能使用this 。所以我认为我应该通过MainWindowViewModel 的当前实例,我已经这样做了。但我仍然没有在输出中得到任何变化。
    • 请查看更新后的问题了解更多详情。我也在 xaml 中声明我的数据上下文。这会影响你提到的代码吗?
    • @Khushi 您确定要更改正确的虚拟机实例吗?如果你在 setter 中设置断点,你看到变化了吗?
    • 是的,我的设置器总是被调用。但是 SelectedObj 的 getter 永远不会被调用。
    • @ReedCopsey 是正确的,您看不到更新值的原因是该值已在 MainWindowViewModel 的不同实例上更新。两个 XAML 文件中的 每次都会创建一个新实例 - 这可以通过在 MainWindowViewModel 的构造函数中插入断点来查看。相反,您应该尝试在 App.xaml 的 ResourceDictionary 中定义 MainWindowViewModel 并在两个 XAML 文件中将其引用为 StaticResource,就像您为 HaemogramTestBorder 对象使用 BorderStyle 一样。这应该会让你明白很多。
    【解决方案2】:

    尝试使用OriginalSource而不是Source:

    var mouseWasDownOn = e.OriginalSource as FrameworkElement;
    

    因为在处理复合控件时Source 属性可以是包含 OriginalSource 对象(在您的情况下为网格)的父级。

    【讨论】:

    • 不,当我调试时,我在那里保留了一个断点,我认为 mouseWasDownOn 中的值始终是我单击的元素。我的意思是我不认为你提到的代码是问题的原因。
    【解决方案3】:

    我认为您的错误可能是 FrameworkElement 没有 Text 属性http://msdn.microsoft.com/en-us/library/system.windows.frameworkelement(v=vs.110).aspx

    编辑:尝试将您对 Text 的绑定更新为

    {Binding SelectedObj.Text}
    

    【讨论】:

    • 是的,你在某种程度上是正确的,所以我用 Object 替换了 FrameworkElement。并且 Object 有一个 Text 属性。
    • 将文本绑定更新为 {Binding SelectedObj.Text} 时会发生什么
    • 我正在检查一下。
    • 我的输出没有任何变化。
    【解决方案4】:

    我认为您的错误可能是您使用的是“通用”属性而不是 DependencyProperties。

    正如你在微软描述中看到的那样

    "当您定义自己的属性并希望它们支持许多 Windows Presentation Foundation (WPF) 功能的各个方面, 包括样式、数据绑定、继承、动画和默认 值,您应该将它们实现为依赖属性。”

    这些是充分利用 WPF 提供的所有资源的正确属性类型

    看看这些链接

    http://msdn.microsoft.com/en-us/library/system.windows.dependencyproperty(v=vs.110).aspx

    http://msdn.microsoft.com/en-us/library/ms750428(v=vs.110).aspx

    或者干脆在google上查找依赖属性WCF。

    了解这些属性之间区别的另一个有用链接是

    https://stackoverflow.com/a/3674727

    我遇到类似问题时使用的。

    希望对你有帮助!

    【讨论】:

      猜你喜欢
      • 2010-09-19
      • 2021-04-23
      • 2014-11-19
      • 2015-11-16
      • 1970-01-01
      • 1970-01-01
      • 2022-01-22
      • 2017-06-14
      • 1970-01-01
      相关资源
      最近更新 更多