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

时间:2018-04-13 13:58:11

标签: c# wpf mvvm

我的View中有一个TabControl,我动态添加包含文本框作为内容的TabItems。当想要从Selected Item中获取Line Count时,它总是返回-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>

我的ViewModel:

TabItem tabItem = new TabItem();
tabItem.Header = mainModel.Header;
TextBox textBox = new TextBox();
textBox.Text = mainModel.TextFile;

tabItem.LayoutUpdated += (sender2, e2) => textBox_LayoutUpdated(sender2, e2);
textBox.LayoutUpdated += (sender3, e3) => textBox_LayoutUpdated(sender3, e3);
tabItem.Content = textBox;
Tabs.Add(tabItem);
SelectedTab = tabItem;

private void textBox_LayoutUpdated(object sender, EventArgs args)
{
 lineCount = ((SelectedTab as TabItem).Content as TextBox).LineCount;
}

MainModel是MVVM中的模型。

我的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的新手,

感谢。

1 个答案:

答案 0 :(得分:1)

您当前的代码存在许多问题。看来你实际上错过了MVVM的观点。

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

您获得-1的原因是您添加到标签项中的TextBox将永远不会被布局,因为您覆盖标签项的ContentTemplate

还有其他一些事情你做错了或者没有必要:

    标签控件Binding
  • ItemsSource不能是TwoWay,因为标签控件本身永远不会更新此属性值。出于同样的原因,此处不需要UpdateSourceTrigger
  • UpdateSourceTrigger绑定不需要
  • SelectedItem,因为它默认为PropertyChanged模式
  • 您有一个名为TextChanged的命令,但按照惯例,它必须命名为TextChangedCommand(带有Command后缀)
  • tabItem.LayoutUpdated += (sender2, e2) => textBox_LayoutUpdated(sender2, e2)对于创建不必要的lambda捕获this的事件订阅来说是一种过度杀手,而是使用方法组语法:tabItem.LayoutUpdated += textBox_LayoutUpdated

现在是真正的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); }
    }
}

此视图模型表示将显示为选项卡项的单个项目。但也许这将是未来的一些其他控制 - 实际上你不必为此烦恼。视图模型不知道哪个控件完全将显示值。视图模型只是以方便的方式提供这些值。

因此,HeaderTextFile属性包含模型值。 LineCount属性将由视图计算(详见下文)。

主视图模型如下所示:

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

    private Item selectedItem;
    public Item SelectedItem
    {
        get => selectedItem;
        set { selectedItem = value; OnPropertyChanged(); }
    }
}

请注意,Items集合属性是只读的。这意味着没有人可以更改对集合的引用,但集合本身不是只读的。但是,SelectedItem引用可能会更新。

现在重点:LineCount的{​​{1}}属性也会更新,例如当TextBox包装文本并调整控件的大小时。所以我们不能只计算视图模型中的行数,我们需要在视图中执行此操作。

但是,正如我们所知,视图模型必须不了解视图。该怎么办?在这种情况下,优秀的开发人员更喜欢TextBox命名空间中的Behavior

让我们创建一个简单的行为来监控System.Windows.Interactivity的{​​{1}}:

LineCount

现在我们可以将此行为附加到任何TextBox并使用行为的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; } } 依赖项属性作为绑定源。这是一个完整的XAML设置:

TextBox

所以这是一个'干净'的MVVM解决方案。希望我能给你一些见解。

顺便说一句,我不知道为什么你需要在视图模型中使用这个行数...