Question How do I bind in this manner? Multi-binding?

glasswizzard

Well-known member
Joined
Nov 22, 2019
Messages
126
Programming Experience
Beginner
Here is an image to help clarify my question.

devenv_JSuauoULmT.png


Three TextBoxes and a TextBlock. I want the TextBlock to be bound to one TextBox and mirror it's contents, but, if I mouse over another TextBox the binding will change so the TextBlock mirrors the Text of that TextBox instead. Mousing over a TextBox is just an arbitrary way to choose one over another. I don't need help with events or anything like that, just the binding. I have one requirement though and that is that the TextBlock be bound to a VM property and never directly to the Text property of a TextBox.

It seems like a very simple thing *fingers crossed*
 
That feels like an awfully complex UI, but my gut feel is that if you simply setup 2 way binding for all the textboxes against their corresponding properties, and each of the properties trigger property change notifications for the other properties, everything will just work out without any deadlocks or infinite loops. The key will be to centralize where where the update and notifications are sent out in the view model (or model).
 
@glasswizzard, I've been busy, and I've a meeting to attend this afternoon for some contract assignments I'm doing for a Youtube affiliate.

If you are still in need of help with this, I will write out a project example for you tomorrow evening, as I might have some free time then.

Also, what did you mean by Mirrored? To be clear, do you want all of the text boxes to concatenate in the textblock for each textbox or simply only change the binding property of the textblock to the new textbox you are hovering on?

In case I can't get back to you :

If you want the binding route, you would set a property to hold the value of whichever textbox you are hovering on, and change the text respectively. The textblock would be bound to your property that the value of the textboxs are set on when they receive mouse focus. By implementing INotifyPropertyChanged as @Skydiver has done in your previous example, you would follow the same design and through which you'd be able to update the binding for whichever one you are hovering on. It's actually straightforward in my opinion.
 
That feels like an awfully complex UI, but my gut feel is that if you simply setup 2 way binding for all the textboxes against their corresponding properties, and each of the properties trigger property change notifications for the other properties, everything will just work out without any deadlocks or infinite loops. The key will be to centralize where where the update and notifications are sent out in the view model (or model).

I tried to do that but I didn't get anywhere with it :/

@glasswizzard, I've been busy, and I've a meeting to attend this afternoon for some contract assignments I'm doing for a Youtube affiliate.

If you are still in need of help with this, I will write out a project example for you tomorrow evening, as I might have some free time then.

I still am incapable of doing this so, any help would be nice, thanks. Take your time though, there's no rush.


Also, what did you mean by Mirrored? To be clear, do you want all of the text boxes to concatenate in the textblock for each textbox or simply only change the binding property of the textblock to the new textbox you are hovering on?

In case I can't get back to you :

If you want the binding route, you would set a property to hold the value of whichever textbox you are hovering on, and change the text respectively. The textblock would be bound to your property that the value of the textboxs are set on when they receive mouse focus. By implementing INotifyPropertyChanged as @Skydiver has done in your previous example, you would follow the same design and through which you'd be able to update the binding for whichever one you are hovering on. It's actually straightforward in my opinion.

I dread to think what wouldn't be straightfoward to you. But yes, it's the binding route I want. The textblock will mirror one textbox only. I'll attempt your suggestion, I feel like I understand it enough to do it, but I felt the same about Skydiver's advice above :) I'll let you know how it goes.

Edit: Ok, I feel really stupid now, I have achieved 99% success with this, I already had the set up you described except the thing I wasn't doing was updating the textblock's property in the textboxes' MouseEnter events, which is ridiculous, why wouldn't I think of that :(

Anyway, I'll show my code. There is one thing I'm not figuring out though and that is how to get the textblock's text to update as each key is pressed while the mouse is over a particular text box. I did try adding "UpdateSourceTrigger=PropertyChanged" to the binding of the TextBlock but it didn't make any difference.

MainWindow.xaml:
<Window x:Class="BindingApp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:BindingApp"
        mc:Ignorable="d"
        Title="MainWindow" Height="108" Width="359">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

        <TextBox Margin="10" Text="{Binding Text1, Mode=TwoWay}" MouseEnter="TextBox_MouseEnter"/>
        <TextBox Grid.Column="1" Margin="10" Text="{Binding Text2, Mode=TwoWay}" MouseEnter="TextBox_MouseEnter"/>
        <TextBox Grid.Column="2" Margin="10" Text="{Binding Text3, Mode=TwoWay}" MouseEnter="TextBox_MouseEnter"/>

        <TextBlock Grid.Row="1" Grid.Column="1" Margin="10" Text="{Binding TextBlockText, FallbackValue=TextBlockText}" HorizontalAlignment="Center"/>

    </Grid>
</Window>

MainWindow.xaml.cs:
namespace BindingApp
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        ViewModel vm = new ViewModel();

        public MainWindow()
        {
            InitializeComponent();
            this.DataContext = vm;
        }

        private void TextBox_MouseEnter(object sender, MouseEventArgs e)
        {
            var textBox = (TextBox)sender;
            
            vm.TextBlockText = textBox.Text;           
        }
    }
}

ViewModel.cs:
namespace BindingApp
{
    public class ViewModel : INotifyPropertyChanged
    {
        private string _text1;       
        public string Text1
        {
            get
            {
                return _text1;
            }
        
            set
            {
                _text1 = value;
                OnPropertyChanged();
            }
        }

        private string _text2;       
        public string Text2
        {
            get
            {
                return _text2;
            }
        
            set
            {
                _text2 = value;
                OnPropertyChanged();
            }
        }

        private string _text3;       
        public string Text3
        {
            get
            {
                return _text3;
            }
        
            set
            {
                _text3 = value;
                OnPropertyChanged();
            }
        }

        private string _textBlockText = "TextBlockText";
        public string TextBlockText
        {
            get { return _textBlockText; }
            set
            {
                _textBlockText = value;
                OnPropertyChanged();
            }
        }


        #region  OnPropertyChanged
        public event PropertyChangedEventHandler PropertyChanged;
        internal void OnPropertyChanged([CallerMemberName] string propName = "") => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
        #endregion
    }
}
 
Last edited:
@GlassWizard: Just passing on a message from Sheepings: Your code looks good, but the binding looks incorrect. He's going to be away for a couple of weeks as he tries to focus on work.

As I get time over the next few days, I'll try to catch up on this thread (and the other thread as well) to see what I can contribute.
 
@glasswizzard I hope you will appreciate that I've taken the time to finish this for you, as I've probably save Skydiver a lot of time by not dumping this code on his lap unfinished, as I also know he has better things to do. And I've taken the time out of my personal work schedule, when I should have been completing work for my contract employer. However, if you need further help with my example, you will need to wait for one of the mods to assist you, as I will be away for the coming weeks. I'm not quite sure for how long. So here is what you asked for :

7YxCiMm.gif


And here is the code I used. Lets start with the Main_ViewModel :
C#:
    public class Main_ViewModel
    {
        public ObservableValue New_Value { get; }
        public Main_ViewModel()
        {
            New_Value = new ObservableValue();
        }
    }
The main view model constructor returns a new class of ObservableValue of the value stored therein : private string TextBlock_Value; of this class. This is done through its GetSetter property :
C#:
        public string TextBlock_Text
        {
            get
            {
                if (string.IsNullOrEmpty(TextBlock_Value))
                    return "Nothing Detected";
                return TextBlock_Value;
            }
            set
            {
                TextBlock_Value = value;
                OnPropertyChanged();
            }
        }
in the ObservableValue class :
C#:
    public class ObservableValue : INotifyPropertyChanged
    {
        private string TextBlock_Value;
        public string TextBlock_Text
        {
            get
            {
                if (string.IsNullOrEmpty(TextBlock_Value))
                    return "Nothing Detected";
                return TextBlock_Value;
            }
            set
            {
                TextBlock_Value = value;
                OnPropertyChanged();
            }
        }
        public event PropertyChangedEventHandler PropertyChanged;
        protected void OnPropertyChanged([CallerMemberName] string new_Value = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(new_Value));
        }
    }
Notice that we also have a public event PropertyChanged -- (Pretty much its a delegate to raise an event which helps with passing the method of the calling code when a component value of the INotifyPropertyChanged has been changed, thus raises the event therein) fires of the below method :
C#:
        protected void OnPropertyChanged([CallerMemberName] string new_Value = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(new_Value));
        }
Quickly moving to the Main Xaml.cs file. I have declared a field : private Main_ViewModel main_ViewModel = new Main_ViewModel();
And in the constructor I have :
C#:
        public Window1()
        {
            InitializeComponent();
            DataContext = main_ViewModel;
            Task Begin = new Task(() => Set_Default_Values());
            Begin.Start();
        }
Notice that I've added my DataContext to equal that of my main_ViewModel which is essentially our main_ViewModel => MainViewModel. Next, (I don't know why), but I update the default value of the textBlock Control by way of a new task. I just wasn't following the guidelines of the MVVM here, but it doesn't really matter as this is another way of updating your UI appropriately. Our Set_Default_Values() method is what first sets the textBlock Text property :
C#:
        private void Set_Default_Values()
        {
            myTextBlock.Dispatcher.Invoke(new Delegate_Callback_To(UpdateUI_With_NewItem), new string[] { "Nothing Detected" });
        }
Because the Task is running on a non UI thread, you will need to call Dispatcher.Invoke() to first invoke the control by passing our : UpdateUI_With_NewItem method to our Delegate. : public delegate void Delegate_Callback_To(string str_Value);. Note that this Delegate takes a string value; for type safety, I've used a string and not a base class object, but you can use whatever object you want for whatever object you are passing in. As you can see, I am passing in the value of "Nothing Detected" to our delegate which will pass along this string from the Delegate Delegate_Callback_To to the UpdateUI_With_NewItem method in the following code : myTextBlock.Dispatcher.Invoke(new Delegate_Callback_To(UpdateUI_With_NewItem), new string[] { "Nothing Detected" });

The UpdateUI_With_NewItem is the method that is used in this instance to update the value of our textBlock :
C#:
        public void UpdateUI_With_NewItem(string item_Value)
        {
            myTextBlock.Text = item_Value;
        }
You should note, that at this point, this has nothing to do with binding, and I am just showing you an alternative safe way to update your UI without the MVVM pattern which I would prefer you to follow. For the sake of pulling it out, and breaking something, I just left it in there, as its also worth knowing how else you can update your UI. No matter what you are doing with your UI, never change values of your UI, or allow worker methods to run on your UI, and always follow either of these principles in this example when working with your UI.

Getting back on track with the MVVM and bindings here. Sorry for the detour but I'd prefer you know and prefer to give it to you as I've wrote it instead of pulling something out and providing you with a broken example, and besides it's also educational for you. And before I continue, I would also like you to look up everything I've highlighted with inline code and italic with the exception of custom code.

Next I added these MouseMove events :
C#:
        private void T0_MouseMove(object sender, MouseEventArgs e)
        {
            main_ViewModel.New_Value.TextBlock_Text = textBox0.Text;
        }

        private void T1_MouseMove(object sender, MouseEventArgs e)
        {
            main_ViewModel.New_Value.TextBlock_Text = textBox1.Text;
        }

        private void T2_MouseMove(object sender, MouseEventArgs e)
        {
            main_ViewModel.New_Value.TextBlock_Text = textBox2.Text;
        }
These events use the Main_ViewModel which depends on the ObservableValue to update the text property of the textBlock. Right so fare I've covered all the classes and explained how it works. You should also note that you will need the following using directives :
C#:
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;

Here is my Xaml :
C#:
<Window xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:WPFTestApp" mc:Ignorable="d"
    x:Class="WPFTestApp.Window1" Title="Window1" x:Name="MyWindow1">
    <Window.DataContext>
        <local:Main_ViewModel/>
    </Window.DataContext>


    <ScrollViewer x:Name="ScrollView_Main" VerticalScrollBarVisibility="Visible" HorizontalScrollBarVisibility="Visible">
        <Grid x:Name="MyGrid" Width="Auto" ShowGridLines="false">
            <Button x:Name="button" Content="Button" HorizontalAlignment="Left" Margin="365,455,0,0" VerticalAlignment="Top" Width="75"/>
            <TextBlock x:Name="myTextBlock" HorizontalAlignment="Left" Margin="215,409,0,0" TextWrapping="Wrap"
                       Text="{Binding New_Value.TextBlock_Text, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" VerticalAlignment="Top"/>
            <TextBox x:Name="textBox0" HorizontalAlignment="Left" Height="23" Margin="35,310,0,0" TextWrapping="Wrap" Text="Hello" VerticalAlignment="Top" Width="120" MouseMove="T0_MouseMove"/>
            <TextBox x:Name="textBox1" HorizontalAlignment="Left" Height="23" Margin="180,310,0,0" TextWrapping="Wrap" Text="My" VerticalAlignment="Top" Width="120" MouseMove="T1_MouseMove"/>
            <TextBox x:Name="textBox2" HorizontalAlignment="Left" Height="23" Margin="320,310,-21,0" TextWrapping="Wrap" Text="Wizzard" VerticalAlignment="Top" Width="120" MouseMove="T2_MouseMove"/>
        </Grid>
    </ScrollViewer>
</Window>

Sorry I didn't do anything fancy here like Skydiver has done in previous examples with custom templating etc. However, the important parts to note here is what I have added in the way for the bindings to actually work. And they are :
  • DataContext which we also set in our Codebehind file.
  • Binding : Which point to the sub properties/methods of whatever your Model consists of. Ie : Text="{Binding New_Value.TextBlock_Text, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Note on the last bullet that the binding starts with the property New_Value : public ObservableValue New_Value { get; } of our Main Main_ViewModel. I've also set the mode as TwoWay, and importantly added : UpdateSourceTrigger=PropertyChanged to this binding to work. Please remember, I will be away for some time as I am not a hobbyist programmer and one who is contracted out, so it may be a while before I come back. Any questions you have will have to be answered by one of the other contributors, that includes any corrections you may need help altering.

By the time you finish following the instructions, you should have something that looks like the full .cs file as follows :
C#:
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;

namespace WPFTestApp
{
    public partial class Window1 : Window
    {
        private Main_ViewModel main_ViewModel = new Main_ViewModel();
        public Window1()
        {
            InitializeComponent();
            DataContext = main_ViewModel;
            Task Begin = new Task(() => Set_Default_Values());
            Begin.Start();
        }

        public delegate void Delegate_Callback_To(string str_Value);

        public void UpdateUI_With_NewItem(string item_Value)
        {
            myTextBlock.Text = item_Value;
        }

        private void Set_Default_Values()
        {
            myTextBlock.Dispatcher.Invoke(new Delegate_Callback_To(UpdateUI_With_NewItem), new string[] { "Nothing Detected" });
        }
        private void T0_MouseMove(object sender, MouseEventArgs e)
        {
            main_ViewModel.New_Value.TextBlock_Text = textBox0.Text;
        }

        private void T1_MouseMove(object sender, MouseEventArgs e)
        {
            main_ViewModel.New_Value.TextBlock_Text = textBox1.Text;
        }

        private void T2_MouseMove(object sender, MouseEventArgs e)
        {
            main_ViewModel.New_Value.TextBlock_Text = textBox2.Text;
        }
    }
    public class ObservableValue : INotifyPropertyChanged
    {
        private string TextBlock_Value;
        public string TextBlock_Text
        {
            get
            {
                if (string.IsNullOrEmpty(TextBlock_Value))
                    return "Nothing Detected";
                return TextBlock_Value;
            }
            set
            {
                TextBlock_Value = value;
                OnPropertyChanged();
            }
        }
        public event PropertyChangedEventHandler PropertyChanged;
        protected void OnPropertyChanged([CallerMemberName] string new_Value = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(new_Value));
        }
    }
    public class Main_ViewModel
    {
        public ObservableValue New_Value { get; }
        public Main_ViewModel()
        {
            New_Value = new ObservableValue();
        }
    }
}
As an aside I'd advise breaking out of nesting classes in one cs file as I have done, and create a hierarchy folder structure for your models, business logic, etc - Learn to read it by debugging it, and take note of the quote in my signature.

Speak to you all soon.
 
Wow, thank you very much for taking the time to do that, this is gonna give me something to chew on for a while.

If anyone has an answer there's one small thing that leaves me scratching my head, why use a scrollviewer? It seems unnecessary, is there a reason it's there?
 
The scroll viewer is there due to the absolute positioning and sizing used for the various pieces in the grid and support window resizing. If you let the grid do the placement and sizing, and set a fixed size for the window, you shouldn't need the scroll viewer.
 
Back
Top Bottom