none
Text anders abschneiden, als dies mit Eigenschaft TextTrimming möglich ist RRS feed

  • Frage

  • Hallo,
    ein ausgewählter Eintrag der ComboBox kann manchmal zu lang sein und wird dann nach der Auswahl abgeschnitten angezeigt.
    Das DataTemplate habe ich überschrieben mit einem TextBlock und der Eigenschaft TextTrimming:

    <TextBlock Text="{Binding DataX}" TextTrimming="WordEllipsis"/>

    Die drei Punkte erscheinen jetzt rechts vom Text:
    C:\Test\TestVerzeichnis\...
     
    Da aber der Dateiname wichtiger ist als das Laufwerk mit den ersten Verzeichnissen, wäre es besser, wenn der Text links abgeschnitten wird:
    ...\TestVerzeichnis\Testdatei.doc

    Oder noch besser ist, wenn Laufwerk und Dateiname angezeigt werden und der Text zwischen beiden gekürzt angezeigt wird:
    C:\Test\...\Testdatei.doc

    Hat jemand für die beiden letzten Fälle eine Lösung (XAML) oder einen Lösungsansatz?
    Beachtet werden muss, dass die Breite der ComboBox variabel ist.

    Alexander

    Dienstag, 26. November 2013 09:01

Antworten

  • Hallo,
    ich fand bei meiner recherche nach einer Lösung eine andere Frage. Da es unter .NET 4.5 extreme Probleme mit dem TextBlock gab, habe ich mal etwas weiter getestet und nachfolgende Klasse erstellt. Diese funktionierte in meinen Tests soweit ganz gut:

    /// <summary>
    /// Stellt eine TextBox zum anzeigen eines Pfades dar. Der Pfad wird automatisch auf die maximal anzeigbare Länge gekürzt.
    /// </summary>
    public class PathTextBox : TextBox
    {
        /// <summary>
        /// Ruft einen Wert ab, der angibt ob der Pfad gekürzt werden soll oder legt diesen Wert fest.
        /// </summary>
        public bool ShortIt
        {
            get { return (bool)GetValue(ShortItProperty); }
            set { SetValue(ShortItProperty, value); }
        }

        /// <summary>
        /// Bezeichnet <see cref="ShortIt"/> Abhängigkeitseigenschaft.
        /// </summary>
        public static readonly DependencyProperty ShortItProperty =
            DependencyProperty.Register("ShortIt", typeof(bool), typeof(PathTextBox), new PropertyMetadata(true, OnShortItPropertyChanged));

        //Path-Eigenschaft wird geändert
        private static void OnShortItPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            (d as PathTextBox).UpdateText();
        }

        /// <summary>
        /// Erstellt eine neue Instanz der <see cref="PathTextBox"/>-Klasse.
        /// </summary>
        public PathTextBox()
        {
            //Standardwerte setzen
            this.BorderBrush = Brushes.Transparent;
            this.BorderThickness = new Thickness(0);
            this.Background = Brushes.Transparent;
            this.IsReadOnly = true;
            this.SizeChanged += PathTrimmingTextBlock_SizeChanged;
        }

        /// <summary>
        /// Ruft den anzuzeigenden Pfad ab oder legt diesen fest. Dies ist eine Abhängigkeitseigenschaft.
        /// </summary>
        public string Path
        {
            get { return (string)GetValue(PathProperty); }
            set { SetValue(PathProperty, value); }
        }

        /// <summary>
        /// Bezeichnet <see cref="Path"/> Abhängigkeitseigenschaft.
        /// </summary>
        public static readonly DependencyProperty PathProperty =
            DependencyProperty.Register("Path", typeof(string), typeof(PathTextBox), new PropertyMetadata("PFAD", OnPathPropertyChanged));

        //Path-Eigenschaft wird geändert
        private static void OnPathPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            (d as PathTextBox).UpdateText();
        }

        // Größe wird geändert...
        void PathTrimmingTextBlock_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            UpdateText();
        }
        /// <summary>
        /// Aktualisiert den Text anhand der <see cref="Path"/>-Eigenschaft.
        /// </summary>
        void UpdateText()
        {
            this.Text = GetTrimmedPath();
        }

        /// <summary>
        /// Gibt den angepassten Pfad zurück.
        /// </summary>
        string GetTrimmedPath()
        {
            if (this.ActualWidth == 0 || !this.ShortIt)
                return this.Path;

            StringBuilder sb = new StringBuilder();
            List<string> parts = this.Path.Split('\\').ToList();
            string fileName = parts.Last(); parts.Remove(fileName);
            bool fileOnly = true;//Nur der Dateiname
            while (parts.Count > 0 && CheckSize(sb.ToString() + parts[0] + @"\...\" + fileName + "XYZ"))
            {
                sb.Append(parts[0]);
                sb.Append(@"\");
                parts.RemoveAt(0);
                fileOnly = false;
            }
            if (parts.Count > 0 && !fileOnly)
                sb.Append(@"...\");
            sb.Append(fileName);
            return sb.ToString();
        }

        /// <summary>
        /// Überprüft ob ein Text angezeigt werden könnte.
        /// </summary>
        bool CheckSize(string text)
        {
            FormattedText formatted = new FormattedText(text,
                    CultureInfo.CurrentCulture,
                    System.Windows.FlowDirection.LeftToRight,
                    this.FontFamily.GetTypefaces().First(),
                    this.FontSize,
                    Brushes.Black);
            return formatted.Width < this.ActualWidth;
        }
    }

    Im DataTemplate musst du die TextBox nur noch "übermalen", damit man die Items auch ordentlich markieren kann:

    <DataTemplate>
        <Grid>
            <l:PathTextBox Path="{Binding }"/>
            <Border HorizontalAlignment="Stretch" VerticalAlignment="Stretch" BorderThickness="0" Background="Transparent" />
        </Grid>
    </DataTemplate>

    Das Popup der ComboBox ist jetzt auch immer nur so breit wie das ausgewählte Item. Aus diesem Grund habe ich ShortIt-Eigenschaft über RelativeSource an die IsOpen-Eigenschaft des ComboBox-Popup's gebunden:

    <DataTemplate>
        <Grid HorizontalAlignment="Stretch">
            <l:PathTextBox Path="{Binding }"
                            ShortIt="{Binding IsOpen, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Popup}}, Converter={StaticResource NagationConverter}}"/>
            <Border HorizontalAlignment="Stretch" VerticalAlignment="Stretch" BorderThickness="0" Background="Transparent" />
        </Grid>
    </DataTemplate>

    Der NagationConverter negiert einfqach einen Wahrheitswert:

    /// <summary>
    /// Negiert einen Wahrheitswert.
    /// </summary>
    public class NagationConverter : IValueConverter
    {
        /// <summary>
        /// Negiert einen Wahrheitswert.
        /// </summary>
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return !(bool)value;
        }
    
        /// <summary>
        /// Negiert einen Wahrheitswert.
        /// </summary>
        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return !(bool)value;
        }
    }

    Viel Spaß damit :)

    PS: Hier noch ein Screenshot des Ganzen:


    Koopakiller [kuːpakɪllɐ] (Tom Lambert)
    Webseite | Code Beispiele | Facebook | Twitter | Snippets   C# ↔ VB.NET Konverter
    Markiert bitte beantwortende Posts als Antwort und bewertet Beiträge. Danke.


    Dienstag, 26. November 2013 20:33
    Moderator

Alle Antworten

  • Hallo,
    ich fand bei meiner recherche nach einer Lösung eine andere Frage. Da es unter .NET 4.5 extreme Probleme mit dem TextBlock gab, habe ich mal etwas weiter getestet und nachfolgende Klasse erstellt. Diese funktionierte in meinen Tests soweit ganz gut:

    /// <summary>
    /// Stellt eine TextBox zum anzeigen eines Pfades dar. Der Pfad wird automatisch auf die maximal anzeigbare Länge gekürzt.
    /// </summary>
    public class PathTextBox : TextBox
    {
        /// <summary>
        /// Ruft einen Wert ab, der angibt ob der Pfad gekürzt werden soll oder legt diesen Wert fest.
        /// </summary>
        public bool ShortIt
        {
            get { return (bool)GetValue(ShortItProperty); }
            set { SetValue(ShortItProperty, value); }
        }

        /// <summary>
        /// Bezeichnet <see cref="ShortIt"/> Abhängigkeitseigenschaft.
        /// </summary>
        public static readonly DependencyProperty ShortItProperty =
            DependencyProperty.Register("ShortIt", typeof(bool), typeof(PathTextBox), new PropertyMetadata(true, OnShortItPropertyChanged));

        //Path-Eigenschaft wird geändert
        private static void OnShortItPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            (d as PathTextBox).UpdateText();
        }

        /// <summary>
        /// Erstellt eine neue Instanz der <see cref="PathTextBox"/>-Klasse.
        /// </summary>
        public PathTextBox()
        {
            //Standardwerte setzen
            this.BorderBrush = Brushes.Transparent;
            this.BorderThickness = new Thickness(0);
            this.Background = Brushes.Transparent;
            this.IsReadOnly = true;
            this.SizeChanged += PathTrimmingTextBlock_SizeChanged;
        }

        /// <summary>
        /// Ruft den anzuzeigenden Pfad ab oder legt diesen fest. Dies ist eine Abhängigkeitseigenschaft.
        /// </summary>
        public string Path
        {
            get { return (string)GetValue(PathProperty); }
            set { SetValue(PathProperty, value); }
        }

        /// <summary>
        /// Bezeichnet <see cref="Path"/> Abhängigkeitseigenschaft.
        /// </summary>
        public static readonly DependencyProperty PathProperty =
            DependencyProperty.Register("Path", typeof(string), typeof(PathTextBox), new PropertyMetadata("PFAD", OnPathPropertyChanged));

        //Path-Eigenschaft wird geändert
        private static void OnPathPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            (d as PathTextBox).UpdateText();
        }

        // Größe wird geändert...
        void PathTrimmingTextBlock_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            UpdateText();
        }
        /// <summary>
        /// Aktualisiert den Text anhand der <see cref="Path"/>-Eigenschaft.
        /// </summary>
        void UpdateText()
        {
            this.Text = GetTrimmedPath();
        }

        /// <summary>
        /// Gibt den angepassten Pfad zurück.
        /// </summary>
        string GetTrimmedPath()
        {
            if (this.ActualWidth == 0 || !this.ShortIt)
                return this.Path;

            StringBuilder sb = new StringBuilder();
            List<string> parts = this.Path.Split('\\').ToList();
            string fileName = parts.Last(); parts.Remove(fileName);
            bool fileOnly = true;//Nur der Dateiname
            while (parts.Count > 0 && CheckSize(sb.ToString() + parts[0] + @"\...\" + fileName + "XYZ"))
            {
                sb.Append(parts[0]);
                sb.Append(@"\");
                parts.RemoveAt(0);
                fileOnly = false;
            }
            if (parts.Count > 0 && !fileOnly)
                sb.Append(@"...\");
            sb.Append(fileName);
            return sb.ToString();
        }

        /// <summary>
        /// Überprüft ob ein Text angezeigt werden könnte.
        /// </summary>
        bool CheckSize(string text)
        {
            FormattedText formatted = new FormattedText(text,
                    CultureInfo.CurrentCulture,
                    System.Windows.FlowDirection.LeftToRight,
                    this.FontFamily.GetTypefaces().First(),
                    this.FontSize,
                    Brushes.Black);
            return formatted.Width < this.ActualWidth;
        }
    }

    Im DataTemplate musst du die TextBox nur noch "übermalen", damit man die Items auch ordentlich markieren kann:

    <DataTemplate>
        <Grid>
            <l:PathTextBox Path="{Binding }"/>
            <Border HorizontalAlignment="Stretch" VerticalAlignment="Stretch" BorderThickness="0" Background="Transparent" />
        </Grid>
    </DataTemplate>

    Das Popup der ComboBox ist jetzt auch immer nur so breit wie das ausgewählte Item. Aus diesem Grund habe ich ShortIt-Eigenschaft über RelativeSource an die IsOpen-Eigenschaft des ComboBox-Popup's gebunden:

    <DataTemplate>
        <Grid HorizontalAlignment="Stretch">
            <l:PathTextBox Path="{Binding }"
                            ShortIt="{Binding IsOpen, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Popup}}, Converter={StaticResource NagationConverter}}"/>
            <Border HorizontalAlignment="Stretch" VerticalAlignment="Stretch" BorderThickness="0" Background="Transparent" />
        </Grid>
    </DataTemplate>

    Der NagationConverter negiert einfqach einen Wahrheitswert:

    /// <summary>
    /// Negiert einen Wahrheitswert.
    /// </summary>
    public class NagationConverter : IValueConverter
    {
        /// <summary>
        /// Negiert einen Wahrheitswert.
        /// </summary>
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return !(bool)value;
        }
    
        /// <summary>
        /// Negiert einen Wahrheitswert.
        /// </summary>
        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return !(bool)value;
        }
    }

    Viel Spaß damit :)

    PS: Hier noch ein Screenshot des Ganzen:


    Koopakiller [kuːpakɪllɐ] (Tom Lambert)
    Webseite | Code Beispiele | Facebook | Twitter | Snippets   C# ↔ VB.NET Konverter
    Markiert bitte beantwortende Posts als Antwort und bewertet Beiträge. Danke.


    Dienstag, 26. November 2013 20:33
    Moderator
  • Hallo Alexander,

    Ich gehe davon aus, dass die Antwort Dir weitergeholfen hat.
    Wenn nicht, neue Rückfragen oder Ergänzungen zu diesem Thread bleiben weiterhin möglich.

    Danke und viele Grüße,
    Ionut


    Ionut Duma, MICROSOFT   Bitte haben Sie Verständnis dafür, dass im Rahmen dieses Forums, welches auf dem Community-PrinzipEntwickler helfen Entwickler“ beruht, kein technischer Support geleistet werden kann oder sonst welche garantierten Maßnahmen seitens Microsoft zugesichert werden können.

    Montag, 2. Dezember 2013 15:39
    Moderator
  • Hallo Koopakiller,

    funktioniert sehr gut. Vielen Dank.

    Alexander

    Dienstag, 3. Dezember 2013 17:29