2017-12-25 46 views
-2

내부에 디렉토리/파일 경로가있는 texbox를 만들고 싶습니다. 디렉토리 경로가 너무 길면 텍스트가 줄임표로 잘리는 것처럼 보입니다. 줄임표가 경로 문자열 중간에 나타나기를 원합니다. 예를 들어 D:\Directory1\Directory2\Directory3D:\...\Directory3으로 다듬을 수 있습니다. 경로 자체는 ViewModel에 바인딩되어야 MVVM 모델에서 사용할 수 있습니다.mvvm을 사용하여 wpf에 줄임표가있는 텍스트 상자를 자르는 경로를 만드는 방법은 무엇입니까?

답변

0

최근에이 문제가 발생하여 여기에서 해결책을 공유하기로 결정했습니다. 코드가 분명하다 있도록이 스레드 How to create a file path Trimming TextBlock with Ellipsis에서 영감을 모든 우선 내가 내 사용자 지정 줄임표와의 텍스트를 잘라 것 TextBlock을 만들기로 결정,이 구현은, 내가 의견을 썼다 : 이제

using System.ComponentModel; 
using System.Globalization; 
using System.Linq; 
using System.Threading.Tasks; 
using System.Windows; 
using System.Windows.Controls; 
using System.Windows.Media; 

namespace PathTrimming.Controls 
{ 
    public class PathTrimmingTextBlock : TextBlock, INotifyPropertyChanged 
    { 
     #region Dependency properties 
     //This property represents the Text of this textblock that can be bound to another viewmodel property, 
     //whenever this property is updated the Text property will be updated too. 
     //We cannot bind to Text property directly because once we update Text, e.g., Text = "NewValue", the binding will be broken 
     public string BoundedText 
     { 
      get { return GetValue(BoundedTextProperty).ToString(); } 
      set { SetValue(BoundedTextProperty, value); } 
     } 

     public static readonly DependencyProperty BoundedTextProperty = DependencyProperty.Register(
      nameof(BoundedText), typeof(string), typeof(PathTrimmingTextBlock), 
      new PropertyMetadata(string.Empty, new PropertyChangedCallback(BoundedTextProperty_Changed))); 

     //Every time the property BoundedText is updated two things should be done: 
     //1) Text should be updated to be equal to new BoundedText 
     //2) New path should be trimmed again 
     private static void BoundedTextProperty_Changed(object sender, DependencyPropertyChangedEventArgs e) 
     { 
      var pathTrimmingTextBlock = (PathTrimmingTextBlock)sender; 
      pathTrimmingTextBlock.OnPropertyChanged(nameof(BoundedText)); 
      pathTrimmingTextBlock.Text = pathTrimmingTextBlock.BoundedText; 
      pathTrimmingTextBlock.TrimPathAsync(); 
     } 
     #endregion 

     private const string Ellipsis = "..."; 


     public PathTrimmingTextBlock() 
     { 
      // This will make sure if the directory name is too long it will be trimmed with ellipsis on the right side 
      TextTrimming = TextTrimming.CharacterEllipsis; 

      //setting the event handler for every time this PathTrimmingTextBlock is rendered 
      Loaded += new RoutedEventHandler(PathTrimmingTextBox_Loaded); 
     } 

     private void PathTrimmingTextBox_Loaded(object sender, RoutedEventArgs e) 
     { 
      //asynchronously update Text, so that the window won't be frozen 
      TrimPathAsync(); 
     } 

     private void TrimPathAsync() 
     { 
      Task.Run(() => Dispatcher.Invoke(() => TrimPath())); 
     } 

     private void TrimPath() 
     { 
      var isWidthOk = false; //represents if the width of the Text is short enough and should not be trimmed 
      var widthChanged = false; //represents if the width of Text was changed, if the text is short enough at the begging it should not be trimmed 
      var wasTrimmed = false; //represents if Text was trimmed at least one time 

      //in this loop we will be checking the current width of textblock using FormattedText at every iteration, 
      //if the width is not short enough to fit textblock it will be shrinked by one character, and so on untill it fits 
      do 
      { 
       //widthChanged? Text + Ellipsis : Text - at first iteration we have to check if Text is not already short enough to fit textblock, 
       //after widthChanged = true, we will have to measure the width of Text + Ellipsis, because ellipsis will be added to Text 
       var formattedText = new FormattedText(widthChanged ? Text + Ellipsis : Text, 
        CultureInfo.CurrentCulture, 
        FlowDirection.LeftToRight, 
        new Typeface(FontFamily, FontStyle, FontWeight, FontStretch), 
        FontSize, 
        Foreground); 

       //check if width fits textblock RenderSize.Width, (cannot use Width here because it's not set during rendering, 
       //and cannot use ActualWidth either because it is the initial width of Text not textblock itself) 
       isWidthOk = formattedText.Width < RenderSize.Width; 

       //if it doesn't fit trim it by one character 
       if (!isWidthOk) 
       { 
        wasTrimmed = TrimPathByOneChar(); 
        widthChanged = true; 
       } 
       //continue loop 
      } while (!isWidthOk && wasTrimmed); 

      //Format Text with ellipsis, if width was changed (after previous loop we may have gotten a path like this "D:\Dire\Directory" 
      //it should be formatted to "D:\...\Directory") 
      if (widthChanged) 
      { 
       FormatWithEllipsis(); 
      } 
     } 

     //Trim Text by one character before last slash, if Text doesn't have slashes it won't be trimmed with ellipsis in the middle, 
     //instead it will be trimmed with ellipsis at the end due to having TextTrimming = TextTrimming.CharacterEllipsis; in the constructor 
     private bool TrimPathByOneChar() 
     { 
      var lastSlashIndex = Text.LastIndexOf('\\'); 
      if (lastSlashIndex > 0) 
      { 
       Text = Text.Substring(0, lastSlashIndex - 1) + Text.Substring(lastSlashIndex); 
       return true; 
      } 
      return false; 
     } 

     //"\Directory will become "...\Directory" 
     //"Dire\Directory will become "...\Directory"\ 
     //"D:\Dire\Directory" will become "D:\...\Directory" 
     private void FormatWithEllipsis() 
     { 
      var lastSlashIndex = Text.LastIndexOf('\\'); 
      if (lastSlashIndex == 0) 
      { 
       Text = Ellipsis + Text; 
      } 
      else if (lastSlashIndex > 0) 
      { 
       var secondastSlashIndex = Text.LastIndexOf('\\', lastSlashIndex - 1); 
       if (secondastSlashIndex < 0) 
       { 
        Text = Ellipsis + Text.Substring(lastSlashIndex); 
       } 
       else 
       { 
        Text = Text.Substring(0, secondastSlashIndex + 1) + Ellipsis + Text.Substring(lastSlashIndex); 
       } 
      } 
     } 

     //starndard implementation of INotifyPropertyChanged to be able to notify BoundedText property change 
     #region INotifyPropertyChanged 
     public event PropertyChangedEventHandler PropertyChanged; 

     public void OnPropertyChanged(string propertyName) 
     { 
      if (PropertyChanged != null) 
      { 
       PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); 
      } 
     } 
     #endregion 
    } 
} 

texblock을 가진 후 우리는 어떤 식 으로든 TextBoxXAML에 "연결"해야한다고했습니다. ControlTemplate을 사용하여 만들 수 있습니다. 이것은 전체 XAML 코드, 내가 다시는 함께 따라하기 쉬운한다, 그래서 코멘트를 썼다 :

<Window x:Class="PathTrimming.MainWindow" 
     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:viewmodel = "clr-namespace:PathTrimming.ViewModel" 
     xmlns:controls="clr-namespace:PathTrimming.Controls" 
     mc:Ignorable="d" 
     Title="MainWindow" Height="350" Width="525"> 
    <!-- Assigning datacontext to the window --> 
    <Window.DataContext> 
     <viewmodel:MainViewModel/> 
    </Window.DataContext> 


    <Window.Resources> 
     <ResourceDictionary> 
      <!--This is the most important part, if TextBox is not in focused, 
      it will be rendered as PathTrimmingTextBlock, 
      if it is focused it shouldn't be trimmed and will be rendered as default textbox. 
      To achieve this I'm using DataTrigger and ControlTemplate--> 
      <Style x:Key="TextBoxDefaultStyle" TargetType="{x:Type TextBox}"> 
       <Style.Triggers> 
        <DataTrigger Binding="{Binding IsKeyboardFocused, RelativeSource={RelativeSource Self}}" Value="False"> 
         <Setter Property="Template"> 
          <Setter.Value> 
           <ControlTemplate TargetType="TextBox"> 
            <Border 
             BorderThickness="1" 
             BorderBrush="#000"> 
             <controls:PathTrimmingTextBlock BoundedText="{TemplateBinding Text}"/> 
            </Border> 
           </ControlTemplate> 
          </Setter.Value> 
         </Setter> 
        </DataTrigger> 
       </Style.Triggers> 
      </Style> 
     </ResourceDictionary> 
    </Window.Resources> 

    <!--Grid with two textboxes and button that updates the textboxes with new pathes from a random path pool--> 
    <Grid> 
     <Grid.ColumnDefinitions> 
      <ColumnDefinition Width="Auto"/> 
     </Grid.ColumnDefinitions> 
     <Grid.RowDefinitions> 
      <RowDefinition Height="Auto"/> 
      <RowDefinition Height="Auto"/> 
      <RowDefinition Height="Auto"/> 
     </Grid.RowDefinitions> 
     <TextBox Grid.Row="0" Grid.Column="0" Width="100" Text="{Binding Path1}" Style="{StaticResource TextBoxDefaultStyle}"/> 
     <TextBox Grid.Row="1" Grid.Column="0" Width="100" Text="{Binding Path2}" Style="{StaticResource TextBoxDefaultStyle}"/> 
     <Button Grid.Row="2" Content="Update pathes" Command="{Binding UpdatePathesCmd}"/> 
    </Grid> 
</Window> 

지금 남아 마지막으로 무엇을 작성하는 것입니다 우리의 View에 데이터를 공급하는 책임이있다 ViewModel. 여기서는 코드를 단순화하기 위해 MVVM Light 라이브러리를 사용했으나 다른 방법을 사용하면 문제가되지 않습니다. 이것은 주석이있는 코드이며, 어쨌든 자명해야합니다.

using GalaSoft.MvvmLight; 
using GalaSoft.MvvmLight.Command; 
using System; 
using System.Windows.Input; 

namespace PathTrimming.ViewModel 
{ 
    public class MainViewModel : ViewModelBase 
    { 
     public string Path1 
     { 
      get { return _path1; } 
      set 
      { 
       _path1 = value; 
       RaisePropertyChanged(); 
      } 
     } 

     public string Path2 
     { 
      get { return _path2; } 
      set 
      { 
       _path2 = value; 
       RaisePropertyChanged(); 
      } 
     } 

     private string _path1; 
     private string _path2; 

     public MainViewModel() 
     { 
      UpdatePathes(); 
     } 

     //The command that will update Path1 and Path2 with some random path values 
     public ICommand UpdatePathesCmd 
     { 
      get { return new RelayCommand(UpdatePathes); } 
     } 

     private void UpdatePathes() 
     { 
      Path1 = PathProvider.GetPath(); 
      Path2 = PathProvider.GetPath(); 
     } 
    } 

    //A simple static class to provide a pool of different pathes 
    public static class PathProvider 
    { 
     private static Random randIndexGenerator = new Random(); 
     private static readonly string[] pathes = 
     { 
      "D:\\Directory1\\Directory2\\Directory3", 
      "D:\\Directory1\\Directory2", 
      "Directory1\\Directory2\\Directory3", 
      "D:\\Directory1\\Directory123456789", 
      "Directory123456789", 
      "D:\\Directory1" 
     }; 

     public static string GetPath() 
     { 
      var randIndex = randIndexGenerator.Next(pathes.Length); 
      return pathes[randIndex]; 
     } 
    } 
}