How to bind to the DataContext ViewModel of another view?

48 views Asked by At

I would like to validate Numbers entered in a TextBox (A) using values coming from another Textbox (B). In the Validation is checked whether the value is a) within a constant value range (-> "Min" and "Max") and b) is smaller than a limit value (-> "UserHighLimit") coming from the user through TextBox B. This TextBox B is part of a different view (-> UserControl "SetLimit") of the same window. To illustrate that scenario, please find the simplified code below.

UserControl (View) "SetValue":

    <UserControl.Resources>
        <libraries:NumberToStringConverter x:Key="NumberToString"/>
    </UserControl.Resources>

    <UserControl.DataContext>
        <viewmodels:ViewModelValue/>
    </UserControl.DataContext>

    <GroupBox Header="Set Value">
        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="1*"/>
                <ColumnDefinition Width="1*"/>
            </Grid.ColumnDefinitions>

            <Label HorizontalAlignment="Right" VerticalAlignment="Center" Content="desired value"/>

            <TextBox Grid.Column="1" Width="70" HorizontalAlignment="Left" VerticalAlignment="Center">
                <TextBox.Text>
                    <Binding
                        Path="Range"
                        UpdateSourceTrigger="PropertyChanged"
                        Converter="{StaticResource NumberToString}">
                        <Binding.ValidationRules>
                            <libraries:ValueRule Min="1" Max="1000">
                                <libraries:ValueRule.ViewModel>
                                    <viewmodels:ViewModelLimit/>
                                </libraries:ValueRule.ViewModel>
                            </libraries:ValueRule>
                        </Binding.ValidationRules>
                    </Binding>
                </TextBox.Text>
            </TextBox>

        </Grid>

    </GroupBox>

UserControl (View) "SetLimit":

    <UserControl.Resources>
        <libraries:NumberToStringConverter x:Key="NumberToString"/>
        <libraries:LimitRule x:Key="ValidateLimit" Min="1" Max="1000"/>
    </UserControl.Resources>

    <UserControl.DataContext>
        <viewmodels:ViewModelLimit/>
    </UserControl.DataContext>

    <GroupBox Header="Set Limit">
        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="1*"/>
                <ColumnDefinition Width="1*"/>
            </Grid.ColumnDefinitions>

            <Label HorizontalAlignment="Right" VerticalAlignment="Center" Content="Limit:"/>

            <TextBox Grid.Column="1" Width="70" HorizontalAlignment="Left" VerticalAlignment="Center">
                <TextBox.Text>
                    <Binding
                        Path="UserHighLimit"
                        UpdateSourceTrigger="PropertyChanged"
                        Converter="{StaticResource NumberToString}">
                        
                        <Binding.ValidationRules>

                            <StaticResource ResourceKey="ValidateLimit"/>

                        </Binding.ValidationRules>
                            
                    </Binding>
                </TextBox.Text>
            </TextBox>

        </Grid>

    </GroupBox>

validation using the bound ViewModel:

    public class ValueRule : ValidationRule
    {
        public ushort Min { get; set; }
        public ushort Max { get; set; }
        public ViewModelLimit ViewModel { get; set; }

        public override ValidationResult Validate(object value, CultureInfo cultureInfo)
        {
            ushort uservalue = 0;
            ushort uhlimit = ViewModel.UserHighLimit;

            try
            {
                if (((string)value).Length > 0)
                    uservalue = ushort.Parse((String)value);
            }
            catch (Exception e)
            {
                return new ValidationResult(false, $"Illegal characters or {e.Message}");
            }
            switch (uhlimit > Max)
            {
                case true:
                    if ((uservalue < Min) || (uservalue > Max))
                    {
                        return new ValidationResult(false, $"Please enter a value in the range: {Min}-{Max}.");
                    }
                    break;
                case false:
                    if (uservalue < Min || uservalue > uhlimit)
                    {
                        return new ValidationResult(false, $"Please enter a value in the range: {Min}-{uhlimit}.");
                    }
                    break;
            }

            return ValidationResult.ValidResult;

The given code is running without exception, but the binding of the ViewModel (-> "ViewModelLimit") for my TextBox validation seem to instantiate an own object. In consequence the limit value ("UserHighLimit") is always the inital value from the constructor of the ViewModel, even if in the ViewModel instance which is the DataContext the limit was already changed. I possible I would like to keep the CodeBehind of my vews clean and instantiate DataContext through XAML.

1

There are 1 answers

0
EldHasp On

Validation Rule is not a Dependency Object, so binding its properties is impossible in principle.
From your question it is not clear how dynamic your creation of ViewModels is. If they are created once, when the window starts, then you can create their instances in the window resources and then get them from there.

    <Window.Resources>
        <local:MainViewModel x:Key="vm"/>
        <viewmodels:ViewModelValue x:Key="vmValue"/>
        <viewmodels:ViewModelLimit x:Key="vmLimit"/>
    </Window.Resources>
<UserControl x:Class="*************.SetValue"
             **********************
             DataContext="{StaticResource vmValue}">
<UserControl x:Class="*************.SetLimit"
             **********************
             DataContext="{StaticResource vmLimit}">
 <Binding.ValidationRules>
     <libraries:ValueRule Min="1" Max="1000" ViewModel="{StaticResource vmLimit}"/>
 </Binding.ValidationRules>

I would also recommend, if possible for your application, creating instances in App resources.

If the creation of instances occurs dynamically (they are created many times, the connections between them are different), then most likely you will not be able to implement this only within XAML. As Clemens wrote above, you will need to create some kind of general logical structure that unites your ViewModels.