2

I have a MainWindow.xaml, MainwindowViewModel.cs, HaemogramReport.xaml and HaemogramReport.xaml.cs. I have other files as well in my project, but the problem lies in the above mentioned four files. I am posting the minimal code here so that others can catch the problem.

Now in HaemogramReport.xaml I declare some controls like Grid, TextBox, TextBlock, Rectangle, Border, ContentControl etc.

For example HaemogramReport.xaml looks like:

<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>

When I click on any of the element in the above declared elements, the mousedown event of the grid named Root is raised.

That event handler is in HaemogramReport.xmal.cs. Here it is:

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

In mouseDown handler of Grid named Root, I say mwvm.SelectedObj = mouseWasDownOn;

SelectedObj is a property of type FrameworkElement which is declared in MainwindowViewModel.cs as follows:

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

Now in my MainWindow I have for example a grid and a textBox inside it. The problematic bindings are declared here. xaml looks like:

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

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

When using the above code, I always get the Text Error in above TextBox.

At the first chance I thought that this might be the binding error, so I changed my MainWindowViewModel.cs as follows:

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

After making the above changes when I run my project I can see 123 in textbox but when I click on any element the text in the textbox does not change.

Now the question here is that if its a binding error then why in second example I get 123 in textbox while in 1st example I get Error - the fallback value.

And if it's not a binding error then what is the problem in above code?

Update

When I debug, I found that get part of SelectedObj is never called. But I don't know why?

Update -- Reed Copsey

Here is my new class:

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

}

And I use it like:

private void Object_Selection(object sender, MouseButtonEventArgs e)
{
    DesignMethods.FindCurrentlyClickedElement(Root, e, this.DataContext as MainWindowViewModel);
}
Khushi
  • 1,031
  • 4
  • 24
  • 48

4 Answers4

2

The problem is you're creating a new instance of the ViewModel, not using the existing one:

// 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;

Note that I would likely not use the term "ViewModel" here, though. What you are doing is very much not a typical MVVM scenario as you're tightly coupling your DataContext instance into your View, with coupling happening in both directions, which is pretty much the opposite of the normal goals of MVVM.


Edit:

You may also need to update your bindings for SelectedObj. I would recommend trying with the XAML set to:

<Grid>
    <TextBox Text="{Binding SelectedObj.Text, UpdateSourceTrigger=PropertyChanged, TargetNullValue='null', FallbackValue='Error'}"/>
</Grid>
Reed Copsey
  • 554,122
  • 78
  • 1,158
  • 1,373
  • I asked this question 2 days ago. Now I have moved `Object_Selection` in a new class, so that I can use it at many places. Also I have marked this method as `static`. And I can't use `this `. So I think I should pass the Current Instance of the `MainWindowViewModel` and I have done so. But still I do not get any changes in the output. – Khushi Jan 08 '14 at 21:23
  • Please see the updated question for more details. Also I am declaring my datacontext in xaml. Does that affect the code you mentioned? – Khushi Jan 08 '14 at 21:27
  • @Khushi Are you sure that you're changing the proper instance of the VM? If you put a breakpoint in the setter, do you see the change? – Reed Copsey Jan 08 '14 at 21:38
  • Yes, my setter is always invoked. But getter of SelectedObj is never invoked. – Khushi Jan 08 '14 at 21:40
  • @Khushi Check my latest - try updating the bindings in the xaml, too – Reed Copsey Jan 08 '14 at 21:57
  • I have tried that and still dont get any changes to the output. – Khushi Jan 08 '14 at 22:21
  • 1
    @ReedCopsey is correct that the reason you don't see the updated value is that the value was updated on a different instance of the MainWindowViewModel. in your two XAML files creates a new instance each time - this can be seen by inserting a break point in the constructor of MainWindowViewModel. Instead you should try defining a MainWindowViewModel in your App.xaml's ResourceDictionary and referencing it in both XAML files as a StaticResource much like you do with BorderStyle for you HaemogramTestBorder object. This should clear up a lot for you. – Tim Erickson Jan 09 '14 at 06:16
  • @TimErickson I have got it. Your idea leads me to success. Thank you. – Khushi Jan 09 '14 at 09:44
0

Try to use the OriginalSource instead of Source:

var mouseWasDownOn = e.OriginalSource as FrameworkElement;

because the Source property when dealing with composite controls, can be the parent that contains the OriginalSource object (in your case the grid).

Alberto
  • 15,626
  • 9
  • 43
  • 56
  • No, When I debug I kept a breakpoint there and I think the value in mouseWasDownOn is always the element on which I click on. I mean I don't think that the code you mentioned is the cause of the problem. – Khushi Jan 07 '14 at 06:45
0

I think your error might be that FrameworkElement doesn't have a Text property http://msdn.microsoft.com/en-us/library/system.windows.frameworkelement(v=vs.110).aspx

enter image description here

EDIT: Try updating your binding on Text to be

{Binding SelectedObj.Text}
DLeh
  • 23,806
  • 16
  • 84
  • 128
0

I think your mistake might be that you are using "common" properties instead of DependencyProperties.

As you can see on Microsoft Description

"When you define your own properties and want them to support many aspects of Windows Presentation Foundation (WPF) functionality, including styles, data binding, inheritance, animation, and default values, you should implement them as a dependency property."

These are the correct types of property to fully use all resources provided by WPF

Take a look at these links

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

Or simply look for Dependency Property WCF on google.

Another useful link to understand the difference between these properties is

https://stackoverflow.com/a/3674727

which I used when I had similar problems.

Hope it helps!

Community
  • 1
  • 1
Vinicius
  • 541
  • 3
  • 15