C# 在WPF上使用MVVM模式时,TextBox.LineCount和TextBox.GetLastVisibleLineIndex()始终为-1

C# 在WPF上使用MVVM模式时,TextBox.LineCount和TextBox.GetLastVisibleLineIndex()始终为-1,c#,wpf,mvvm,C#,Wpf,Mvvm,我的视图中有一个TabControl,我动态添加TabItems,其中包含一个文本框作为内容。当需要从所选项目获取行数时,它总是返回-1,同时返回textbox.GetLastVisibleLineIndex()。代码如下: 我的看法是: <TabControl x:Name="tabControl" HorizontalAlignment="Left" Height="385" Margin="5,50,0,0" VerticalAlignment="Top" Width="740" I

我的视图中有一个TabControl,我动态添加TabItems,其中包含一个文本框作为内容。当需要从所选项目获取行数时,它总是返回-1,同时返回textbox.GetLastVisibleLineIndex()。代码如下:

我的看法是:

<TabControl x:Name="tabControl" HorizontalAlignment="Left" Height="385" Margin="5,50,0,0" VerticalAlignment="Top" Width="740" ItemsSource="{Binding Tabs, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" SelectedItem="{ Binding SelectedTab, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}">
        <TabControl.ItemTemplate>
            <!-- this is the header template-->
            <DataTemplate>
                <TextBlock
                Text="{Binding Header}" />
            </DataTemplate>
        </TabControl.ItemTemplate>
        <TabControl.ContentTemplate>
            <!-- this is the body of the TabItem template-->
            <DataTemplate>
                <TextBox
                Text="{Binding Text, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged,diag:PresentationTraceSources.TraceLevel=High }" AcceptsReturn="True" >
                    <i:Interaction.Triggers>
                        <i:EventTrigger EventName="KeyUp">
                            <cmd:EventToCommand Command="{Binding DataContext.TextChanged, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ItemsControl}}}" PassEventArgsToCommand="True" />
                        </i:EventTrigger>
                    </i:Interaction.Triggers>
                 </TextBox>
            </DataTemplate>
        </TabControl.ContentTemplate>
    </TabControl>
MainModel是我在MVVM中的模型

My View.cs:

this.UpdateLayout();
TabItem tab = this.tabControl.SelectedItem as TabItem;
int index = ((this.tabControl.SelectedItem as TabItem).Content as TextBox).GetLastVisibleLineIndex();
即使在这里,View.cs始终为-1

我是WPF MVVM的新手


谢谢。

您当前的代码有很多问题。看起来你实际上错过了MVVM的要点

首先,视图模型不能知道视图,也不能知道任何视图特定的类型(例如
TabItem
)。视图模型只是一个层,它采用您的模型,以便该模型可以由视图显示。视图模型不能像您在示例中那样构造视图本身

得到-1的原因是,您添加到选项卡项中的
文本框将永远不会被布局,因为您覆盖了选项卡项的
内容模板

还有一些事情你要么做错了,要么是不必要的:

  • 选项卡控件的
    itemsource
    Binding
    不能是双向的,因为选项卡控件本身永远不会更新此属性值。出于同样的原因,此处不需要
    更新资源记录器
  • SelectedItem
    绑定不需要
    UpdateSourceTrigger
    ,因为默认情况下它具有
    PropertyChanged
    模式
  • 您有一个名为
    TextChanged
    的命令,但按照惯例,它必须命名为
    TextChangedCommand
    (使用
    命令
    后缀)
  • tabItem.LayoutUpdated+=(sender2,e2)=>textBox\u LayoutUpdated(sender2,e2)
    对于创建不必要的lambda捕获的事件订阅来说是一种过度使用
    tabItem.LayoutUpdated+=textBox\u layoututututdated
现在是真正的MVVM解决方案: 假设您拥有项目的视图模型:

class Item : ViewModelBase
{
    public Item(string header, string textFile)
    {
        Header = header;
        this.textFile = textFile;
    }

    public string Header { get; }

    private string textFile;
    public string TextFile
    {
        get => textFile;
        set { textFile = value; OnPropertyChanged(); }
    }

    private int lineCount;
    public int LineCount
    {
        get => lineCount;
        set { lineCount = value; OnPropertyChanged(); Debug.WriteLine("Line count is now: " + value); }
    }
}
此视图模型表示将显示为选项卡项的单个项。但也许这将是未来的另一种控制——实际上你不必为此烦恼。视图模型不知道哪个控件确切地显示值。视图模型只是以一种方便的方式提供这些值

因此,
标题
文本文件
属性包含模型值。
LineCount
属性将由视图计算(有关详细信息,请参见下文)

其主视图模型如下所示:

class ViewModel : ViewModelBase
{
    public ObservableCollection<Item> Items { get; } = new ObservableCollection<Item>();

    private Item selectedItem;
    public Item SelectedItem
    {
        get => selectedItem;
        set { selectedItem = value; OnPropertyChanged(); }
    }
}
现在,我们可以将此行为附加到任何
文本框
,并使用该行为的
行计数
依赖项属性作为绑定源。以下是完整的XAML设置:

<TabControl ItemsSource="{Binding Items}" SelectedItem="{Binding SelectedItem, Mode=TwoWay}">
    <TabControl.ItemTemplate>
        <DataTemplate DataType="local:Item">
            <TextBlock Text="{Binding Header}" />          
        </DataTemplate>
    </TabControl.ItemTemplate>
    <TabControl.ContentTemplate>
        <DataTemplate DataType="local:Item">
            <TextBox Text="{Binding TextFile, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" AcceptsReturn="True" TextWrapping="Wrap">
                <i:Interaction.Behaviors>
                    <local:LineCountBehavior LineCount="{Binding LineCount, Mode=OneWayToSource}"/>
                </i:Interaction.Behaviors>
            </TextBox>
        </DataTemplate>
    </TabControl.ContentTemplate>
</TabControl>

因此,这是一个“干净”的MVVM解决方案。希望我能给你一些见解


顺便说一句,我不知道为什么在视图模型中需要此行计数…

如果返回-1,则表示
文本框
布局信息不可用。您是否在
LayoutUpdated
函数中设置了断点,以查看是否实际调用了该函数?您正在检查是否存在任何问题。延迟调用或选择更好的事件来驱动它。是的,我在那里有一个断点,当我生成scroll事件时它被命中,然后我在该事件处理程序中调用UpdateLayout()。首先,我向TabControl添加一个TabItem,然后当我滚动文本框时,我需要查看行数和最后可见的行索引。但它们始终为-1,即使在scroll事件处理程序中对TabItem调用UpdateLayout()。名称空间现在是
Microsoft.Xaml.Behaviors
,而不是
System.Windows.Interactivity
。您需要添加
xmlns:i=”http://schemas.microsoft.com/xaml/behaviors“
到您的XAML。
class LineCountBehavior : Behavior<TextBox>
{
    public int LineCount
    {
        get { return (int)GetValue(LineCountProperty); }
        set { SetValue(LineCountProperty, value); }
    }

    public static readonly DependencyProperty LineCountProperty =
    DependencyProperty.Register("LineCount", typeof(int), typeof(LineCountBehavior), new PropertyMetadata(0));

    protected override void OnAttached()
    {
        AssociatedObject.LayoutUpdated += RefreshLineCount;
        AssociatedObject.TextChanged += RefreshLineCount;
    }

    protected override void OnDetaching()
    {
        AssociatedObject.LayoutUpdated -= RefreshLineCount;
        AssociatedObject.TextChanged -= RefreshLineCount;
    }

    private void RefreshLineCount(object sender, EventArgs e)
    {
      LineCount = AssociatedObject.LineCount;
    }
}
<TabControl ItemsSource="{Binding Items}" SelectedItem="{Binding SelectedItem, Mode=TwoWay}">
    <TabControl.ItemTemplate>
        <DataTemplate DataType="local:Item">
            <TextBlock Text="{Binding Header}" />          
        </DataTemplate>
    </TabControl.ItemTemplate>
    <TabControl.ContentTemplate>
        <DataTemplate DataType="local:Item">
            <TextBox Text="{Binding TextFile, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" AcceptsReturn="True" TextWrapping="Wrap">
                <i:Interaction.Behaviors>
                    <local:LineCountBehavior LineCount="{Binding LineCount, Mode=OneWayToSource}"/>
                </i:Interaction.Behaviors>
            </TextBox>
        </DataTemplate>
    </TabControl.ContentTemplate>
</TabControl>