@romaro

Как передать содержимое TextBox во вьюмодель, если этот TextBox вложен в контрол?

Вопрос, видимо, похож на этот.

У меня есть кастомный контол, который выводит подсказку для текста. XAML:
<UserControl x:Name="textBoxWithPlaceholder" x:Class="LibControls.TextBoxWithPlaceholder"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:LibControls"
             mc:Ignorable="d"
             Background="White" >

    <!-- Мой вариант UserControl
                    На основе контейнера Grid c использованием тригеров -->

    <!--Общий контейнер визуально объединяющий TextBlock и TextBox-->

    <Grid x:Name="PART_grid"
          >

        <!--TextBlock для текста по умолчанию
                        Foreground - Цвет шрифта для текста по умолчанию-->
        <TextBlock x:Name="PART_TextBlock" 
                    Foreground="{Binding DefaultTextBrush, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:TextBoxWithPlaceholder}}}" 
                    Margin="5,0" Text="{Binding DefaultText, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:TextBoxWithPlaceholder}}}"
                    FontStyle="{Binding FontStyle, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:TextBoxWithPlaceholder}}}"
                    FontSize="{Binding FontSize, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:TextBoxWithPlaceholder}}}"
                    FontFamily="{Binding FontFamily, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:TextBoxWithPlaceholder}}}"
                    FontStretch="{Binding FontStretch, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:TextBoxWithPlaceholder}}}"
                   />

         <!--TextBox для ввода--> 
        <TextBox x:Name="PART_TextBox"     Background="{x:Null}" 
                 Foreground="{Binding Foreground, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:TextBoxWithPlaceholder}}}" 
                 FontStyle="{Binding FontStyle, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:TextBoxWithPlaceholder}}}"
                 FontSize="{Binding FontSize, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:TextBoxWithPlaceholder}}}"
                 FontFamily="{Binding FontFamily, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:TextBoxWithPlaceholder}}}"
                 FontStretch="{Binding FontStretch, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:TextBoxWithPlaceholder}}}"
                 Text="{Binding Text, Mode=TwoWay, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:TextBoxWithPlaceholder}}, UpdateSourceTrigger=PropertyChanged}" TextChanged="PART_TextBox_TextChanged"
                 BorderThickness="0"/>
    </Grid>

</UserControl>


CS:
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;

namespace LibControls
{
    /// <summary>Логика взаимодействия для TextBoxWithPlaceholder.xaml</summary>
    public partial class TextBoxWithPlaceholder : UserControl
    {
        public TextBoxWithPlaceholder()
        {
            InitializeComponent();
        }

...

        /// <summary>
        /// Свойство для текста ввода
        /// </summary>
        public string Text
        {
            get { return (string)GetValue(TextProperty); }
            set {
                SetValue(TextProperty, value);
            }
        }
        public static readonly DependencyProperty TextProperty =
            DependencyProperty.Register("Text", typeof(string), typeof(TextBoxWithPlaceholder), new PropertyMetadata(null));

        /// <summary>
        /// Обработчик события изменения текста, устанавливающий
        /// видимость/невидимость текста подсказки
        /// </summary>
        private void PART_TextBox_TextChanged(object sender, TextChangedEventArgs e)
        {
            if (string.IsNullOrWhiteSpace(Text)) PART_TextBlock.Visibility = Visibility.Visible;
            else PART_TextBlock.Visibility = Visibility.Hidden;
        }
    }
}


Этот контрол я добавляю на форму:
<Window x:Class="GuiBuyerDesktop.Windows.LoginWindow.LoginWindowView"
        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:ctx="clr-namespace:GuiBuyerDesktop.Windows.LoginWindow"
        xmlns:lib="clr-namespace:LibControls;assembly=LibControls"
        mc:Ignorable="d">
...
            <lib:TextBoxWithPlaceholder
                Style="{StaticResource BaseInputStyle}"
                DefaultText="Логин"
                DefaultTextBrush="LightGray"
                Text="{Binding Path=Login, UpdateSourceTrigger=PropertyChanged}"
                />
...
</Window>


Но биндинг не срабатывает, значение из вложенного TextBox не передается во вьюмодель:
using System;
using System.Windows;
using WPFCore;

namespace GuiBuyerDesktop.Windows.LoginWindow
{
    internal class LoginWindowContext : BaseViewModel
    {
        private string _Login = String.Empty;
...

        public string Login
        {
            get => _Login;
            set {
                MessageBox.Show("Checked!");
                Set(ref _Login, value);
            }
        }

...
    }
}


Не могу понять, как это правильно обойти? Через всплывающие события?
  • Вопрос задан
  • 25 просмотров
Пригласить эксперта
Ответы на вопрос 1
@pkrich
Вот так всё работает:

<UserControl 
    x:Name="textBoxWithPlaceholder"
    x:Class="LibControls.TextBoxWithPlaceholder"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    Padding="5,0"
    mc:Ignorable="d"
    d:Width="200"
    d:Height="30"
    Background="White" >

    <!-- Мой вариант UserControl На основе контейнера Grid c использованием тригеров -->

    <!--Общий контейнер визуально объединяющий TextBlock и TextBox-->

    <Grid x:Name="PART_grid">

        <!--TextBlock для текста по умолчанию
                        Foreground - Цвет шрифта для текста по умолчанию-->
        <TextBlock
            x:Name="PART_TextBlock" 
            Foreground="LightGray" 
            Text="{Binding DefaultText, ElementName=textBoxWithPlaceholder}"
            FontStyle="{Binding FontStyle, ElementName=textBoxWithPlaceholder}"
            FontSize="{Binding FontSize, ElementName=textBoxWithPlaceholder}"
            FontFamily="{Binding FontFamily, ElementName=textBoxWithPlaceholder}"
            FontStretch="{Binding FontStretch, ElementName=textBoxWithPlaceholder}"/>

        <!--TextBox для ввода-->

        <TextBox
            x:Name="PART_TextBox"
            Background="Transparent" 
            Foreground="{Binding Foreground, ElementName=textBoxWithPlaceholder}" 
            FontStyle="{Binding FontStyle, ElementName=textBoxWithPlaceholder}"
            FontSize="{Binding FontSize, ElementName=textBoxWithPlaceholder}"
            FontFamily="{Binding FontFamily, ElementName=textBoxWithPlaceholder}"
            FontStretch="{Binding FontStretch, ElementName=textBoxWithPlaceholder}"
            Text="{Binding Text, ElementName=textBoxWithPlaceholder, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
            BorderThickness="0"/>
    </Grid>

</UserControl>


using System;
using System.Windows;
using System.Windows.Controls;

namespace LibControls
{
    /// <summary>
    /// Interaction logic for TextBoxWithPlaceholder.xaml
    /// </summary>
    public partial class TextBoxWithPlaceholder : UserControl
    {
        public TextBoxWithPlaceholder()
        {
            InitializeComponent();
        }



        public string DefaultText
        {
            get => (string)GetValue(DefaultTextProperty);
            set => SetValue(DefaultTextProperty, value);
        }

        // Using a DependencyProperty as the backing store for DefaultText.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty DefaultTextProperty =
            DependencyProperty.Register("DefaultText",
                typeof(string), typeof(TextBoxWithPlaceholder));



        /// <summary>
        /// Свойство для текста ввода
        /// </summary>
        public string Text
        {
            get => (string)GetValue(TextProperty);
            set => SetValue(TextProperty, value);
        }

        public static readonly DependencyProperty TextProperty =
            DependencyProperty.Register("Text",
                typeof(string), typeof(TextBoxWithPlaceholder),
                new PropertyMetadata(Text_Changed));

        /// <summary>
        /// Обработчик события изменения текста, устанавливающий
        /// видимость/невидимость текста подсказки
        /// </summary>
        private static void Text_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (d is TextBoxWithPlaceholder control
                && e.NewValue is string text)
            {
                if (string.IsNullOrWhiteSpace(text))
                {
                    control.PART_TextBlock.Visibility = Visibility.Visible;
                }
                else
                {
                    control.PART_TextBlock.Visibility = Visibility.Hidden;
                }
            }
        }
    }
}


<Window 
	x:Name="mainWindow"
	x:Class="LibControls.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:LibControls"
    mc:Ignorable="d"
	d:DataContext="{d:DesignData Type=local.LoginWindowContext, IsDesignTimeCreatable=False}"
	Title="MainWindow" Height="450" Width="800">
    <DockPanel
        HorizontalAlignment="Center"
        VerticalAlignment="Center">
        <local:TextBoxWithPlaceholder
            BorderBrush="Black"
            BorderThickness="1"
            Margin="30"
            Padding="20 5"
			DefaultText="Логин"
			Text="{Binding Path=Login,Mode=TwoWay}"/>
        <TextBox
            BorderBrush="Black"
            BorderThickness="1"
            Margin="30"
            Padding="20 5"
            HorizontalAlignment="Center"
            VerticalAlignment="Center"
			Text="{Binding Path=Login,Mode=TwoWay}"/>
    </DockPanel>
</Window>


using System;
using System.Windows;

namespace LibControls
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            DataContext = new LoginWindowContext();
        }
    }
}


using System.ComponentModel;
using System.Runtime.CompilerServices;

namespace LibControls
{
    internal class LoginWindowContext : INotifyPropertyChanged
    {
        private string _Login = "dfgjhbdsfgjhb";

        public string Login
        {
            get => _Login;
            set
            {
                Set(ref _Login, value);
            }
        }

        public event PropertyChangedEventHandler? PropertyChanged;

        protected bool Set<T>(
            ref T field,
            T newValue,
            [CallerMemberName] string? propertyName = null)
        {
            if (Equals(field, newValue))
            {
                return false;
            }

            field = newValue;

            PropertyChanged?
                      .Invoke(
                          this,
                          new PropertyChangedEventArgs(propertyName));

            return true;
        }
    }
}
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы