4

私が書いているチャットクライアントの場合、次のコントロールを作成したいと思います:

ここに画像の説明を入力

これは、任意のテキストを表示できるユーザーがサイズ変更可能な 3 つの列で構成される必要がありますが、互いに整列されている必要があります (Jeff の発言でわかるように)。

書式設定済みのテキストを表示し、自動的に一番下までスクロールできるカスタムを既に持っていますがRichTextBox、サイズ変更可能な列を含むテキスト ボックスを作成する方法がわかりません (独自のコントロールを作成するのはかなり初めてです)。

何を探すか、または一般的なアイデアについての指針はありますか? どんな助けでも大歓迎です!

4

3 に答える 3

3

Ok。winformsを忘れてください。役に立たず、非推奨で、見苦しく、カスタマイズができず、UI の仮想化とハードウェア レンダリングが不足しているため、非常に遅いです。

これはあなたが説明したことに対する私の見解です:

<Window x:Class="MiscSamples.ThreeColumnChatSample"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:MiscSamples"
        Title="ThreeColumnChatSample" Height="300" Width="300">
    <Window.Resources>
        <local:FlowDocumentToXamlConverter x:Key="DocumentConverter"/>
    </Window.Resources>
    <ListView ItemsSource="{Binding}" ScrollViewer.HorizontalScrollBarVisibility="Hidden">
        <ListView.View>
            <GridView>
                <GridView.Columns>
                    <GridViewColumn DisplayMemberBinding="{Binding DateTime}"/>
                    <GridViewColumn DisplayMemberBinding="{Binding Sender}"/>
                    <GridViewColumn>
                        <GridViewColumn.CellTemplate>
                            <DataTemplate>
                                <FlowDocumentScrollViewer Document="{Binding Content, Converter={StaticResource DocumentConverter}}"
                                                          VerticalScrollBarVisibility="Hidden" HorizontalScrollBarVisibility="Hidden"/>
                            </DataTemplate>
                        </GridViewColumn.CellTemplate>
                    </GridViewColumn>
                </GridView.Columns>
            </GridView>
        </ListView.View>
    </ListView>
</Window>

コードビハインド:

 public partial class ThreeColumnChatSample : Window
    {
        public ObservableCollection<ChatEntry> LogEntries { get; set; }

        private string TestData = "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum";
        private List<string> words;
        private int maxword;
        public Random random { get; set; }

        public ThreeColumnChatSample()
        {
            InitializeComponent();

            random = new Random();
            words = TestData.Split(' ').ToList();
            maxword = words.Count - 1;

            DataContext = LogEntries = new ObservableCollection<ChatEntry>();
            Enumerable.Range(0, 100)
                      .ToList()
                      .ForEach(x => LogEntries.Add(GetRandomEntry()));
        }

        private ChatEntry GetRandomEntry()
        {
            return new ChatEntry()
                {
                    DateTime = DateTime.Now,
                    Sender = words[random.Next(0, maxword)],
                    Content = GetFlowDocumentString(string.Join(" ",Enumerable.Range(5, random.Next(10, 50)).Select(x => words[random.Next(0, maxword)])))
                };
        }

        private string GetFlowDocumentString(string text)
        {
            return "<FlowDocument xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'>" +
                   "   <Paragraph>" +
                   "     <Run Text='" + text + "'/>" +
                   "   </Paragraph>" +
                   "</FlowDocument>";
        }
    }

データ項目:

public class ChatEntry:PropertyChangedBase
{
    public DateTime DateTime { get; set; }

    private string _content;
    public string Content
    {
        get { return _content; }
        set
        {
            _content = value;
            OnPropertyChanged("Content");
        }
    }

    public string Sender { get; set; }
}

PropertyChangedBase (MVVM ヘルパー クラス):

public class PropertyChangedBase:INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged(string propertyName)
    {
        Application.Current.Dispatcher.BeginInvoke((Action) (() =>
                                                                 {
                                                                     PropertyChangedEventHandler handler = PropertyChanged;
                                                                     if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
                                                                 }));
    }
}

結果:

ここに画像の説明を入力

  • この投稿の を使用しましFlowDocumentToXAMLConverter
  • 3 列目のリッチ コンテンツは で示されていますが、リンクされた投稿からFlowDocumentViewerバインド可能なものを使用するように変更できます。RichTextBox
  • ヘッダーの端をクリックしてドラッグすると、列のサイズを変更できます。
  • WPF には UI の仮想化が組み込まれているため、大量の行がある場合でも、アプリケーションがひどく遅れることはありません。
  • ここで説明するソリューションを実装して、含まれているウィンドウのサイズを変更するときに最後の列のサイズを変更することができます。これにより、ワードラップと解像度の独立性が実現されます。
  • コード ビハインドのほとんどは、実際には例をサポートするためのボイラープレートであることに注意してください (ランダム エントリの生成など)。それを取り除くと、本当にきれいな解決策になります。
  • WPF ロックス。私のコードを(リンクされた投稿のコンバーターと一緒に)コピーして貼り付けて File -> New Project -> WPF Application 、結果を自分で確認してください。

編集:

@KingKing の要求に従って、サンプルを変更してチャット クライアントをエミュレートしました。

FsRichTextBox.dll上記のリンクされた CodeProject 投稿からの参照を追加しました。

<Window x:Class="MiscSamples.ThreeColumnChatSample"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:MiscSamples"
        xmlns:rtb="clr-namespace:FsWpfControls.FsRichTextBox;assembly=FsRichTextBox"
        Title="ThreeColumnChatSample" WindowState="Maximized">
    <Window.Resources>
        <local:FlowDocumentToXamlConverter x:Key="DocumentConverter"/>
    </Window.Resources>
    <Grid>

        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition Height="300"/>
        </Grid.RowDefinitions>

        <ListView ItemsSource="{Binding ChatEntries}" ScrollViewer.HorizontalScrollBarVisibility="Hidden"
                  x:Name="ListView">
            <ListView.View>
                <GridView>
                    <GridView.Columns>
                        <GridViewColumn DisplayMemberBinding="{Binding DateTime}"/>
                        <GridViewColumn DisplayMemberBinding="{Binding Sender}"/>
                        <GridViewColumn>
                            <GridViewColumn.CellTemplate>
                                <DataTemplate>
                                    <FlowDocumentScrollViewer Document="{Binding Content, Converter={StaticResource DocumentConverter}}"
                                                          VerticalScrollBarVisibility="Hidden" HorizontalScrollBarVisibility="Hidden"/>
                                </DataTemplate>
                            </GridViewColumn.CellTemplate>
                        </GridViewColumn>
                    </GridView.Columns>
                </GridView>
            </ListView.View>
        </ListView>

        <GridSplitter Height="3" Grid.Row="1" HorizontalAlignment="Stretch" VerticalAlignment="Top"/>

        <DockPanel Grid.Row="1">
            <Button Content="Send" DockPanel.Dock="Right" VerticalAlignment="Bottom" Margin="2"
                    Click="Send_Click"/>

            <rtb:FsRichTextBox Document="{Binding UserInput,Converter={StaticResource DocumentConverter}, Mode=TwoWay}"
                           DockPanel.Dock="Bottom" Height="300" x:Name="InputBox"/>
        </DockPanel>
    </Grid>
</Window>

コードビハインド:

public partial class ThreeColumnChatSample : Window
{
    public ChatViewModel ViewModel { get; set; }

    public ThreeColumnChatSample()
    {
        InitializeComponent();

        DataContext = ViewModel = new ChatViewModel();
    }

    private void Send_Click(object sender, RoutedEventArgs e)
    {
        InputBox.UpdateDocumentBindings();

        var entry = ViewModel.AddEntry();

        ListView.ScrollIntoView(entry);
    }
}

ビューモデル:

public class ChatViewModel:PropertyChangedBase
{
    public ObservableCollection<ChatEntry> ChatEntries { get; set; }
    private string _userInput;
    public string UserInput
    {
        get { return _userInput; }
        set
        {
            _userInput = value;
            OnPropertyChanged("UserInput");
        }
    }

    public string NickName { get; set; }

    public ChatViewModel()
    {
        ChatEntries = new ObservableCollection<ChatEntry>();
        NickName = "John Doe";
    }

    public ChatEntry AddEntry()
    {
        var entry = new ChatEntry {DateTime = DateTime.Now, Sender = NickName};
        entry.Content = UserInput;

        ChatEntries.Add(entry);

        UserInput = null;

        return entry;
    }
}

結果:

ここに画像の説明を入力

于 2013-06-30T18:06:50.207 に答える
1

これが の解決策ですWinforms。私は Winforms の専門家ではありませんが、このソリューションは問題ありません。Winforms の専門家は、誰かが想像できるよりも優れたものにすることができるに違いありません。これを解決して、3 番目の列に 1 しか含まれないようにしましたRichTextBoxが、問題があります。そのHighCore's solutionようには機能しないようです。このソリューションでは、各エントリRichTextBoxに 3 番目の列に特定の1 つが表示されます。

public class ChatWindow : SplitContainer
{
    private SplitContainer innerSpliter = new SplitContainer();
    public ChatWindow()
    {
        Type type = typeof(Panel);
        type.GetProperty("DoubleBuffered", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).SetValue(innerSpliter.Panel2, true, null);
        //Initialize some properties
        innerSpliter.Parent = Panel2;
        innerSpliter.Panel2.AutoScroll = true;
        innerSpliter.Dock = DockStyle.Fill;
        SplitterDistance = 50;
        innerSpliter.SplitterDistance = 10;
        BorderStyle = BorderStyle.FixedSingle;
        innerSpliter.BorderStyle = BorderStyle.FixedSingle;
        //-----------------------------            
        Panel1.BackColor = Color.White;
        innerSpliter.Panel1.BackColor = innerSpliter.Panel2.BackColor = Color.White;
    }
    bool adding;
    private Binding GetTopBinding(RichTextBox richText)
    {
        Binding bind = new Binding("Top", richText, "Location");
        bind.Format += (s, e) =>
        {
            Binding b = s as Binding;                           
            if (adding)
            {
                RichTextBox rtb = b.DataSource as RichTextBox;
                if (rtb.TextLength == 0) { e.Value = ((Point)e.Value).Y; return; }
                rtb.SuspendLayout();
                rtb.SelectionStart = 0;
                int i = rtb.SelectionFont.Height;
                int belowIndex = 0;
                while (belowIndex == 0&&i < rtb.Height-6)
                {
                    belowIndex = rtb.GetCharIndexFromPosition(new Point(1, i++));
                }                                        
                float baseLine1 = 0.75f * i; //This is approximate
                float baseLine2 = GetBaseLine(b.Control.Font, b.Control.CreateGraphics());//This is exact
                b.Control.Tag = (baseLine1 > baseLine2 ? baseLine1 - baseLine2 - 2: 0);
                e.Value = ((Point)e.Value).Y + (float)b.Control.Tag;
                rtb.ResumeLayout(false);
            }
            else e.Value = ((Point)e.Value).Y + (float)b.Control.Tag;
        };
        return bind;
    }
    private Binding GetHeightBinding(RichTextBox richText)
    {
        Binding bind = new Binding("Height", richText, "Size");
        bind.Format += (s, e) =>
        {
            Binding b = s as Binding;
            e.Value = ((Size)e.Value).Height - b.Control.Top + ((RichTextBox) b.DataSource).Top;
        };
        return bind;
    }
    private Binding GetWidthBinding(Panel panel)
    {
        Binding bind = new Binding("Width", panel, "Size");
        bind.Format += (s, e) =>
        {                
            e.Value = ((Size)e.Value).Width;
        };
        return bind;
    }
    public void AddItem(string first, string second, string third)
    {
        adding = true;            
        RichTextBox richText = new RichTextBox();
        innerSpliter.Panel2.SuspendLayout();
        Panel1.SuspendLayout();
        innerSpliter.Panel1.SuspendLayout();

        richText.Dock = DockStyle.Top;
        richText.Width = innerSpliter.Panel2.Width;            
        richText.ContentsResized += ContentsResized;                               
        richText.BorderStyle = BorderStyle.None;
        Label lbl = new Label() { Text = first, AutoSize = false, ForeColor = Color.BlueViolet};            
        lbl.DataBindings.Add(GetHeightBinding(richText));                      
        lbl.DataBindings.Add(GetTopBinding(richText));            
        lbl.DataBindings.Add(GetWidthBinding(Panel1));
        lbl.Parent = Panel1;            
        lbl = new Label() { Text = second,  AutoSize = false, ForeColor = Color.BlueViolet };            
        lbl.DataBindings.Add(GetHeightBinding(richText));            
        lbl.DataBindings.Add(GetTopBinding(richText));            
        lbl.DataBindings.Add(GetWidthBinding(innerSpliter.Panel1));
        lbl.Parent = innerSpliter.Panel1;            
        richText.Visible = false;
        richText.Parent = innerSpliter.Panel2;
        richText.Visible = true;
        richText.Rtf = third;            
        richText.BringToFront();             
        innerSpliter.Panel1.ResumeLayout(true);
        innerSpliter.Panel2.ResumeLayout(true);
        Panel1.ResumeLayout(true);
        innerSpliter.Panel2.ScrollControlIntoView(innerSpliter.Panel2.Controls[0]);
        adding = false;
    }
    private void ContentsResized(object sender, ContentsResizedEventArgs e)
    {
        ((RichTextBox)sender).Height = e.NewRectangle.Height + 6;
    }
    private float GetBaseLine(Font font, Graphics g)
    {
        int lineSpacing = font.FontFamily.GetLineSpacing(font.Style);
        int cellAscent = font.FontFamily.GetCellAscent(font.Style);
        return font.GetHeight(g) * cellAscent / lineSpacing;
    }
}
//I provide only 1 AddItem() method, in fact it's enough because normally we don't have requirement to remove a chat line once it's typed and sent.
chatWindow.AddItem(DateTime.Now.ToString(), "User name", "Rtf text");

また、3 つの列すべてのベースライン (最初の行) を均等化しようとしました。正確なベースラインはGetBaseLineメソッドで見つけることができますが、RichTextBox の最初の行のベースラインは、最初の行のすべての文字をループしてSelectionFont各文字を取得することによってのみ見つけることができます。私はこのアプローチを試しましたが、パフォーマンスはとても悪い(ほとんど受け入れられない)。0.75そこで、固定定数を使用して を掛ける近似計算を試みました。Font Height正確な割合はCellAscent/LineSpacingです。

WinformsOPが解決策ではなく解決策を望んでいることを願っていWPFます。

コントロールのスクリーン ショットを次に示します。 ここに画像の説明を入力

于 2013-07-02T02:41:42.243 に答える
0

考えられる解決策の 1 つは、ListViewコントロールを 3 つの列と詳細ビューで使用することです。これにより、表示された WPF ソリューションとまったく同じ結果が Windows フォームで得られます。

もう 1 つの解決策は、ListView コントロールと同様に、 DataGridViewを使用して 3 つの列を持つテーブルを作成し、新しいイベントごとに行を追加することです。

どちらの場合も、3 番目の列 (メッセージ コンテンツが存在する場所) では、 RichTextBoxなどの適切なテキスト書式設定を行うためにリッチ UI コントロールを使用します。

于 2013-06-30T16:53:21.680 に答える