简体   繁体   中英

WPF Custom Control, best way to split string

I have got a custom property which will have a name entered into it. As you can see below:

UserProfile.cs

using System;
using System.ComponentModel;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;

namespace Controls
{
    public class UserProfile : Control
    {
        #region Fields
        public static readonly DependencyProperty PhotoSizeProperty;
        public static readonly DependencyProperty UserNameProperty;
        #endregion

        static UserProfile()
        {
            // Initialize as lookless control
            DefaultStyleKeyProperty.OverrideMetadata(typeof(UserProfile),
                new FrameworkPropertyMetadata(typeof(UserProfile)));

            // Initialize dependency properties
            PhotoSizeProperty = DependencyProperty.Register("PhotoSize", typeof(Double), typeof(UserProfile), null);
            UserNameProperty = DependencyProperty.Register("UserName", typeof(String), typeof(UserProfile), null);
        }

        #region Custom Control Properties
        /// <summary>
        /// Gets or sets the Label which is displayed next to the field
        /// </summary>
        [Description("Size of the user image"), Category("Common Properties")]
        public Double PhotoSize
        {
            get { return (Double)GetValue(PhotoSizeProperty); }
            set { SetValue(PhotoSizeProperty, value); }
        }

        /// <summary>
        /// Gets or sets the Label which is displayed next to the field
        /// </summary>
        [Description("Username, split first and last names"), Category("Common Properties")]
        public String UserName
        {
            get { return (String)GetValue(UserNameProperty); }
            set { SetValue(UserNameProperty, value); }
        }
        #endregion
    }
    public class CalculateBorder : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            Double parm = System.Convert.ToDouble(parameter);
            return new Thickness((double)value / parm);
        }
        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
    public class CalculateFont : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            Double parm = System.Convert.ToDouble(parameter);
            return (double)value / parm;
        }
        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
}

This is my Generic style for my control. There are two TextBlock's, one needs to have the first name and the second should have the second name. What's the best way to split this name given for this without too much over the top code?

Generic.xaml

<Style TargetType="{x:Type local:UserProfile}">
        <Setter Property="Width" Value="150" />
        <Setter Property="Foreground" Value="White" />
        <Setter Property="FontSize" Value="{Binding Converter={StaticResource CalculateFont}, 
            ConverterParameter=35, 
            RelativeSource={RelativeSource AncestorType={x:Type local:UserProfile}}, 
            Path=(local:UserProfile.PhotoSize)}" />
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type local:UserProfile}">
                    <Grid x:Name="circleGrid" Width="{Binding PhotoSize}">
                        <Grid.RowDefinitions>
                            <RowDefinition Height="{Binding Path=ActualWidth, ElementName=circleGrid}" />
                            <RowDefinition />
                            <RowDefinition />
                        </Grid.RowDefinitions>
                        <Border x:Name="circleBorder"
                            Grid.Row="0"
                            CornerRadius="{Binding Path=ActualWidth, ElementName=circleGrid}"
                            Width="{Binding Path=ActualWidth, ElementName=circleGrid}"
                            Height="{Binding Path=ActualWidth, ElementName=circleGrid}"
                            BorderBrush="White"
                            BorderThickness="{Binding Converter={StaticResource CalculateBorder}, 
                        ConverterParameter=35, 
                        RelativeSource={RelativeSource AncestorType={x:Type local:UserProfile}}, 
                        Path=(local:UserProfile.PhotoSize)}">
                            <Border.Background>
                                <ImageBrush ImageSource="D:\Users\Martyn Ball\Pictures\11061728_10153409797331063_2946862347621203654_o.jpg" Stretch="UniformToFill" />
                            </Border.Background>
                        </Border>

                        <WrapPanel Grid.Row="1" HorizontalAlignment="Center">
                            <TextBlock x:Name="firstName"
                                       FontWeight="Bold"
                                       Foreground="{TemplateBinding Foreground}"
                                       Text="{TemplateBinding UserName}" />
                            <TextBlock Text=" "/>
                            <TextBlock x:Name="lastName"
                                       FontWeight="Normal"
                                       Foreground="{TemplateBinding Foreground}"
                                       Text="{TemplateBinding UserName}" />
                        </WrapPanel>
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

Edit:

Why can't I just create this property like so, SplitUsername ?

/// <summary>
        /// Gets or sets the Label which is displayed next to the field
        /// </summary>
        [Description("Username, split first and last names"), Category("Common Properties")]
        public String UserName
        {
            get { return (String)GetValue(UserNameProperty); }
            set { SetValue(UserNameProperty, value); }
        }
        public string[] SplitUsername
        {
            get { return GetValue(UserNameProperty).ToString().Split(' '); }
        }
        #endregion

I'm trying to Bind to it in my style, and I get an error saying this it isn't recognized or accessible.

Text="{TemplateBinding SplitUsername[0]}"

Edit 2

Okay, so this is the property which should make an array containing [0] => "Firstname, [1] => "Secondname" .

public string[] SplitUsername
{
    get { return GetValue(UserNameProperty).ToString().Split(' '); }
}

And here is my binding, which isn't working:

<TextBlock x:Name="firstName"
            FontWeight="Bold"
            Foreground="{TemplateBinding Foreground}"
            Text="{Binding SplitUsername[0]}" />
<TextBlock Text=" "/>
<TextBlock x:Name="lastName"
            FontWeight="Normal"
            Foreground="{TemplateBinding Foreground}"
            Text="{Binding SplitUsername[1]}" />

I don't seem to be getting any errors!

Edit 3

/// <summary>
        /// Gets or sets the Label which is displayed next to the field
        /// </summary>
        [Description("Username, split first and last names"), Category("Common Properties")]
        public String UserName
        {
            get { return (String)GetValue(UserNameProperty); }
            set { SetValue(UserNameProperty, value); }
        }
        public static readonly DependencyProperty UserNameProperty = DependencyProperty.Register("UserName", typeof(String), typeof(UserProfile), 
                new FrameworkPropertyMetadata(
                    false,
                    new PropertyChangedCallback(UserNamePropertyChanged)));

        private static void UserNamePropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
        {
            UserProfile profile = sender as UserProfile;

            TextBlock firstName = profile.GetTemplateChild("firstName") as TextBlock;
            TextBlock lastName = profile.GetTemplateChild("lastName") as TextBlock;

            if (firstName != null && lastName != null)
            {
                if (args.NewValue == null)
                {
                    firstName.Text = string.Empty;
                    lastName.Text = string.Empty;
                }
                else
                {
                    string newValue = args.NewValue.ToString();

                    if (string.IsNullOrWhiteSpace(newValue))
                    {
                        firstName.Text = string.Empty;
                        lastName.Text = string.Empty;
                    }
                    else
                    {
                        string[] splittedValues = newValue.Split(' ');

                        if (splittedValues.Length == 1)
                        {
                            firstName.Text = newValue;
                            lastName.Text = string.Empty;
                        }
                        else if (splittedValues.Length == 2)
                        {
                            firstName.Text = splittedValues[0];
                            lastName.Text = splittedValues[1];
                        }
                        else if (splittedValues.Length > 2)
                        {
                            firstName.Text = splittedValues[0];
                            lastName.Text = newValue.Substring(splittedValues[0].Length + 1);
                        }
                    }
                }
            }
        }
        #endregion

Use Control's Tag property. Split the full name using Split() and store resulting array in Tag property.

Eg;

<Button x:Name="BtnName" Content="Anjum Khan" />
<TextBlock Background="#FFEECF0A" Text="{Binding Tag[0], ElementName=BtnName}" />
<TextBlock Background="#FF5DF1AE"  Text="{Binding Tag[1], ElementName=BtnName}"  />


BtnName.Tag = BtnName.Content.ToString().Split(new char[] { ' ' });

TextBlocks will show First and Last name respectively. You can build upon this concept.

Your templatebindings will work too :

<TextBlock x:Name="firstName"
           FontWeight="Bold"
           Foreground="{TemplateBinding Foreground}"
           Text="{TemplateBinding Tag[0]}" />

Instead of binding from the control template I would suggest you react on the changed UserName property and set the textboxes in code. Like this:

    /// <summary>
    /// Gets or sets the Label which is displayed next to the field
    /// </summary>
    [Description("Username, split first and last names"), Category("Common Properties")]
    public String UserName
    {
        get { return (String)GetValue(UserNameProperty); }
        set { SetValue(UserNameProperty, value); }
    }

    public static readonly DependencyProperty UserNameProperty = DependencyProperty.Register("UserName", typeof(String), typeof(UserProfile), new PropertyMetadata("Firstname Lastname",UserNamePropertyChanged));

    private static void UserNamePropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
    {
        UserProfile profile = sender as UserProfile;
        profile.RefreshFirstAndLastName();            
    }

    public override void OnApplyTemplate()
    {
        base.OnApplyTemplate();
        this.RefreshFirstAndLastName();
    }

    private void RefreshFirstAndLastName()
    {
        TextBlock firstName = this.GetTemplateChild("firstName") as TextBlock;
        TextBlock lastName = this.GetTemplateChild("lastName") as TextBlock;

        if (firstName != null && lastName != null)
        {
            if (string.IsNullOrWhiteSpace(this.UserName))
            {
                firstName.Text = string.Empty;
                lastName.Text = string.Empty;
            }
            else
            {
                string[] splittedValues = this.UserName.Split(' ');

                if (splittedValues.Length == 1)
                {
                    firstName.Text = this.UserName;
                    lastName.Text = string.Empty;
                }
                else if (splittedValues.Length == 2)
                {
                    firstName.Text = splittedValues[0];
                    lastName.Text = splittedValues[1];
                }
                else if (splittedValues.Length > 2)
                {
                    firstName.Text = splittedValues[0];
                    lastName.Text = this.UserName.Substring(splittedValues[0].Length + 1);
                }
            }
        }
    }

I know this looks odd when working with WPF. But keep in mind you are within a control . You are inside a view and this doesn´t violate MVVM.

Update 1:

I found the issue with your code. Change the definition of the UserProfile in MainWindow.xaml like this:

<Controls:UserProfile PhotoSize="150" UserName="{Binding Text, ElementName=Username}" />

You have to bind to the Text property of the TextBox.

Managed to get it working, this is the binding in Generic.xaml

<TextBlock x:Name="firstName"
            FontWeight="Bold"
            Foreground="{TemplateBinding Foreground}"
            Text="{Binding SplitUsername[0], RelativeSource={RelativeSource TemplatedParent}}" />
<TextBlock Text=" "/>
<TextBlock x:Name="lastName"
            FontWeight="Normal"
            Foreground="{TemplateBinding Foreground}"
            Text="{Binding SplitUsername[1], RelativeSource={RelativeSource TemplatedParent}}" />

This is the code behind in UserProfile.cs

#region Custom Control Properties
/// <summary>
/// Gets or sets the Label which is displayed next to the field
/// </summary>
[Description("Size of the user image"), Category("Common Properties")]
public Double PhotoSize
{
    get { return (Double)GetValue(PhotoSizeProperty); }
    set { SetValue(PhotoSizeProperty, value); }
}

/// <summary>
/// Gets or sets the Label which is displayed next to the field
/// </summary>
[Description("Username, split first and last names"), Category("Common Properties")]
public String UserName
{
    get { return (String)GetValue(UserNameProperty); }
    set { SetValue(UserNameProperty, value); }
}
public string[] SplitUsername
{
    get { return GetValue(UserNameProperty).ToString().Split(' '); }
}
#endregion

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM