Текстовое поле WPF с обрезкой многоточия и украшением проверки

В настоящее время я работаю над пользовательским элементом управления, чтобы добавить дополнительные функции в TextBox:

  • Обрезка с многоточием, если текст слишком большой, а TextBox потерял фокус
  • Метка перед текстовым полем
  • Проверка при ошибке

Я нашел пример многоточия. В этом примере текущее значение TextBox сохраняется в свойстве зависимостей и задается свойство TextBox.Text, если элемент получил/потерял фокус.

Вот мой xaml-код моего пользовательского элемента управления:

<UserControl x:Class="WpfTextBoxEllipsis.EditUC"
         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" 
         mc:Ignorable="d" 
         d:DesignHeight="300" d:DesignWidth="300"
         Name="EDIT"
         Loaded="EditUC_OnLoaded">
<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="Auto"/>
        <ColumnDefinition Width="*"/>
    </Grid.ColumnDefinitions>

    <TextBlock Text="Label" Margin="10,0" Grid.Column="0" VerticalAlignment="Center"/>
    <TextBox Name="Box" Grid.Column="1"
             LostFocus="BoxLostFocus"
             GotFocus="BoxGotFocus"
             LayoutUpdated="BoxOnLayoutUpdated"/>
</Grid>

The code behind of my user control:

public partial class EditUC : UserControl
{
    private string textvalue;

    public EditUC()
    {
        InitializeComponent();
    }

    internal UpdateSourceTrigger UpdateTrigger { get; set; }

    #region Text property

    public static readonly DependencyProperty TextProperty = DependencyProperty.Register(
        "Text",
        typeof(string),
        typeof(EditUC),
        new FrameworkPropertyMetadata(string.Empty)
        {
            DefaultUpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged,
            BindsTwoWayByDefault = true
        });

    public string Text
    {
        get
        {
            return (string)GetValue(TextProperty);
        }

        set
        {
            SetValue(TextProperty, value);
        }
    }

    #endregion

    #region Ellisped TextBox

    public string TextValue
    {
        get
        {
            return this.textvalue;
        }

        set
        {
            if (this.textvalue == value)
            {
                return;
            }

            this.textvalue = value;
            this.Box.Text = this.CutTextToWidth(this.textvalue);
        }
    }

    private string CutTextToWidth(string value)
    {
        if (string.IsNullOrEmpty(value))
        {
            return value;
        }

        var width = this.Box.ActualWidth - 25;
        var validArea = false;
        var shortText = value;
        var lastlong = value.Length;
        var lastfit = 0;

        if (this.StringWidth(value) < width)
        {
            shortText = value;
        }
        else
        {
            while (!validArea)
            {
                if (width < this.StringWidth(shortText + "\u2026"))
                {
                    lastlong = shortText.Length;
                }
                else
                {
                    lastfit = shortText.Length;
                }

                int newLen = (lastfit + lastlong) / 2;

                if (shortText.Length != newLen)
                {
                    shortText = value.Substring(0, newLen);
                }
                else
                {
                    shortText = value.Substring(0, lastfit);
                    break;
                }

                var w = this.StringWidth(shortText + "\u2026");
                validArea = (width - 10 < w) && (w < width);
            }

            shortText += "\u2026";
        }

        return shortText;
    }

    private void BoxGotFocus(object sender, RoutedEventArgs e)
    {
        int index = this.Box.SelectionStart;
        this.Box.Text = this.textvalue;
        this.Box.SelectionStart = index;
        this.Box.TextChanged += this.BoxOnTextChanged;
    }

    private void BoxOnTextChanged(object sender, TextChangedEventArgs args)
    {
        if (sender != this.Box || args.Changes.Count <= 0 || this.UpdateTrigger != UpdateSourceTrigger.PropertyChanged)
        {
            return;
        }

        this.UpdateVM();
    }

    private void UpdateVM()
    {
        this.Text = this.Box.Text;

        ////var exp = BindingOperations.GetBindingExpression(this, TextProperty);
        ////if (exp != null)
        ////{
        ////    exp.UpdateSource();
        ////    bool he = exp.HasError;
        ////}
    }

    private void BoxLostFocus(object sender, RoutedEventArgs e)
    {
        this.Box.TextChanged -= this.BoxOnTextChanged;
        this.UpdateVM();
        this.TextValue = this.Box.Text;
        ToolTipService.SetToolTip(this.Box, this.textvalue);
    }

    private double StringWidth(string s)
    {
        if (s == " ")
        {
            s = "\u00a0";
        }

        var formattedText = new FormattedText(
                s,
                CultureInfo.CurrentUICulture,
                FlowDirection.LeftToRight,
                new Typeface(this.Box.FontFamily, this.Box.FontStyle, this.Box.FontWeight, this.Box.FontStretch),
                this.Box.FontSize,
                Brushes.Black);

        return formattedText.Width;

    }

    private void BoxOnLayoutUpdated(object sender, EventArgs e)
    {
        if (!this.Box.IsFocused)
        {
            var width = this.StringWidth(this.Box.Text);
            if (width > this.Box.ActualWidth || (width + 10 < this.Box.ActualWidth && this.Box.Text != this.TextValue))
            {
                this.Box.Text = this.CutTextToWidth(this.TextValue);
            }
        }
    }

    #endregion

    private void EditUC_OnLoaded(object sender, RoutedEventArgs e)
    {
        this.TextValue = this.Text;

        var exp = BindingOperations.GetBindingExpression(this, TextProperty);
        var parent = exp != null ? exp.ParentBinding : null;
        this.UpdateTrigger = parent != null ? parent.UpdateSourceTrigger : UpdateSourceTrigger.Default;

        if (this.UpdateTrigger == UpdateSourceTrigger.Default)
        {
            var def = TextProperty.GetMetadata(this) as FrameworkPropertyMetadata;
            if (def != null)
            {
                this.UpdateTrigger = def.DefaultUpdateSourceTrigger;
            }
        }

        this.AddHandler(Validation.ErrorEvent, new RoutedEventHandler(OnErrorEvent));
    }

    private void OnErrorEvent(object sender, RoutedEventArgs routedEventArgs)
    {
        if (sender == null)
        {
            return;
        }
    }
}

С помощью следующего кода я получаю красную рамку вокруг всего пользовательского элемента управления в случае ошибок проверки.

<local:EditUC Text="{Binding Text, ValidatesOnDataErrors=True}"/>

Но я хочу, чтобы вокруг TextBox был только значок ошибки.

  1. Есть ли у кого-нибудь решение моей проблемы?

Я установил содержимое TextBox «Box» вручную.

  1. Есть ли возможность использовать привязку TextProperty к Box.Text?
  2. Поможет ли это

И последнее, но не менее важное: моя модель просмотра:

public class MainVM : INotifyPropertyChanged, IDataErrorInfo
{
    private string text;

    public string Text
    {
        get
        {
            return this.text;
        }

        set
        {
            if (this.text == value)
            {
                return;
            }

            this.text = value;
            this.OnPropertyChanged();
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    public string this[string columnName]
    {
        get
        {
            return this.Validate(columnName);
        }
    }

    public string Error
    {
        get
        {
            return "asd";
        }
    }

    private string Validate(string properyName)
    {
        string msg = string.Empty;
        switch (properyName)
        {
            case "Text":
                msg = this.Text == "Valid" ? string.Empty : "Error";
                break;
        }
        return msg;
    }
}

Большое спасибо за твою помощь.

С уважением Кристиан


person mcr76    schedule 03.11.2015    source источник
comment
Ни у кого нет идей?   -  person mcr76    schedule 09.11.2015
comment
Или моя проблема непонятна?   -  person mcr76    schedule 09.11.2015