C# 在WPF上使用MVVM模式时,TextBox.LineCount和TextBox.GetLastVisibleLineIndex()始终为-1
我的视图中有一个TabControl,我动态添加TabItems,其中包含一个文本框作为内容。当需要从所选项目获取行数时,它总是返回-1,同时返回textbox.GetLastVisibleLineIndex()。代码如下: 我的看法是: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 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>