在列表视图中获取父控制器

时间:2018-01-16 13:19:06

标签: c# wpf mvvm

我有一个带有名称列表的ListView,我希望能够通过双击或按钮重命名每个值。

我已经为doubleclick做了这个,并且正在使用它:

WPF

<ListView Grid.Row="0" x:Name="ListProfileView"
          ItemsSource="{Binding ProfilesCollection}">
    <ListView.ItemTemplate>
        <DataTemplate>
            <TextBox Text="{Binding Name}" IsReadOnly="True" VerticalAlignment="Center">
                <i:Interaction.Triggers>
                    <i:EventTrigger EventName="MouseDoubleClick">
                        <i:InvokeCommandAction
                            Command="{Binding DataContext.RenameCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}">
                            <i:InvokeCommandAction.CommandParameter>
                                <MultiBinding Converter="{StaticResource MultiConverter}">
                                    <Binding RelativeSource="{RelativeSource AncestorType={x:Type TextBox}}"/>
                                    <Binding Source="{x:Static classes:BooleanHelper.False}"/>
                                </MultiBinding>
                            </i:InvokeCommandAction.CommandParameter>
                        </i:InvokeCommandAction>
                    </i:EventTrigger>
                    <i:EventTrigger EventName="LostFocus">
                        <i:InvokeCommandAction
                            Command="{Binding DataContext.RenameCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}">
                            <i:InvokeCommandAction.CommandParameter>
                                <MultiBinding Converter="{StaticResource MultiConverter}">
                                    <Binding RelativeSource="{RelativeSource AncestorType={x:Type TextBox}}"/>
                                    <Binding Source="{x:Static classes:BooleanHelper.True}"/>
                                </MultiBinding>
                            </i:InvokeCommandAction.CommandParameter>
                        </i:InvokeCommandAction>
                    </i:EventTrigger>
                </i:Interaction.Triggers>
            </TextBox>
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>

c#(带有ICommand的MVVM模型):

private ICommand _renameCommand;
/// <summary>
/// Command used to change the name of the selected profile.
/// </summary>
public ICommand RenameCommand
{
    get
    {
        return _renameCommand ?? (_renameCommand = new RelayCommand<object>(obj =>
        {
            if(!(obj is object[] values)) return;
            if(!(values[0] is TextBox txtBox) || !(values[1] is bool value)) return;

            txtBox.IsReadOnly = value;
            if (!value)
            {
                txtBox.Focus();
            }
        }));
    }
}

但对于按钮,我不知道如何获取文本框的路径以使用相同的命令。 我尝试过这样的事情:

<Button Grid.Column="3" Content="{x:Static dictionnaries:ColorConfigurationDictionnary.rename}"
        FontWeight="SemiBold"
        Command="{Binding RenameCommand}">
    <Button.CommandParameter>
        <MultiBinding Converter="{StaticResource MultiConverter}">
            <Binding ElementName="ListProfileView" Path="ItemContainerGenerator"/>
            <Binding Source="{x:Static classes:BooleanHelper.False}"/>
        </MultiBinding>
    </Button.CommandParameter>
</Button>

但我没有想法......这可能吗?

2 个答案:

答案 0 :(得分:0)

似乎存在某种错误信息,所以让我描述一下MvvM如何以我能想到的最佳方式工作。
模型是存储数据的地方,所以我们称之为个人资料:

namespace Model
{
    public class Profile
    {
         public string Name { get; set; }
    }
}  

现在你需要的是一个ViewModel,它将提供被操纵数据的信息:

using VM.Commands;

namespace VM
{
    public class MainViewModel : BaseViewModel
    {
        public MainViewModel()
        {
            ProfilesCollection = new List<Profile>();
            for (int i = 0; i < 100; i++)
            {
                ProfilesCollection.Add(new Profile() {Name = $"Name {i}"});
            }
            RenameCommand = new TestCommand(renameCommandMethod, (o) => true);
        }

    void renameCommandMethod(object parameter)// to manipulate the colleciton you use the Commands which you already do but without the need for converters or any UI elements. Makes it much easier to handle.
    {
        string renameTo = parameter.ToString();
        foreach (var profile in ProfilesCollection)
        {
            profile.Name = renameTo;
        }
    }

    private List<Profile> profilesCollection;   

    public List<Profile> ProfilesCollection
    {
        get { return profilesCollection; }
        set { profilesCollection = value; OnPropertyChanged(); }
    }

    private ICommand renameCommand;

    public ICommand RenameCommand
    {
        get { return renameCommand; }
        set { renameCommand = value; }
    }  

RelayCommand

的实施
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;

namespace VM.Commands
{
public class TestCommand : ICommand
{
    private Action<object> _execute;
    private Predicate<object> _canExecute;
    public TestCommand(Action<object> execute, Predicate<object> canExecute)
    {
        _execute = execute;
        _canExecute = canExecute;
    }

    #region Implementation of ICommand

    public bool CanExecute(object parameter)
    {
        return _canExecute?.Invoke(parameter) ?? true;
    }

    public void Execute(object parameter)
    {
        _execute?.Invoke(parameter);
    }

    public event EventHandler CanExecuteChanged;

    #endregion
}
}  

然后UI看起来像这样:

<Window x:Class="SO_app.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:vm="clr-namespace:VM;assembly=VM"
    xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" 
    xmlns:converter="clr-namespace:SO_app.Converters"
    xmlns:validation="clr-namespace:SO_app.Validation"
    xmlns:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase"
    xmlns:local="clr-namespace:SO_app"
    xmlns:sys="clr-namespace:System;assembly=mscorlib"
    xmlns:model="clr-namespace:Model;assembly=Model"
    mc:Ignorable="d"
    d:DataContext="{d:DesignInstance Type=vm:MainViewModel, IsDesignTimeCreatable=True}"
    Title="MainWindow" Height="452.762" Width="525" Closing="Window_Closing">
<Window.Resources>
    <CollectionViewSource Source="{Binding ProfilesCollection}" x:Key="profiles"/>
</Window.Resources>
<Window.DataContext>
    <vm:MainViewModel/>
</Window.DataContext>

<Window.Background>
    <VisualBrush>
        <VisualBrush.Visual>
            <Rectangle Width="50" Height="50" Fill="ForestGreen"></Rectangle>
        </VisualBrush.Visual>
    </VisualBrush>
</Window.Background>
<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition/>
        <ColumnDefinition/>
    </Grid.ColumnDefinitions>
    <ListView ItemsSource="{Binding Source={StaticResource profiles}}" 
              VirtualizingPanel.VirtualizationMode="Recycling">
        <ListView.ItemTemplate>
            <DataTemplate>
                <DataTemplate.Resources>
                    <ToolTip x:Key="Tip">
                        <TextBlock>
                            <Run>Some text here</Run>
                            <LineBreak/>
                            <Run Text="{Binding Name, StringFormat='Actual Text: {0}'}"/>
                        </TextBlock>
                    </ToolTip>
                </DataTemplate.Resources>
                <TextBlock Text="{Binding Name}" ToolTip="{StaticResource Tip}"/>
            </DataTemplate>
        </ListView.ItemTemplate>
    </ListView>
    <StackPanel Grid.Column="1">
        <Button Content="Rename" Command="{Binding RenameCommand}" CommandParameter="NewName"></Button>
    </StackPanel>
</Grid>



这给你的是:
*无需任何转换器清洁UI *每个操作都在ViewModel中完成,不传递任何UI元素 *在UI中,您可以使用动画样式或为文本元素设置字体。但要避免在那里处理点击。这是可能的,有时无法避免,但尝试利用ViewModel来操纵数据 顺便说一句,这里没有控制器。
如果您有任何问题,请询问。

答案 1 :(得分:0)

这是我做的: 这让我只通过禁用列表视图中文本框的只读来更改一个值的名称。 我在后面的GUI代码中写道。

private ICommand _renameCommand;
/// <summary>
/// Command used to change the name of the selected profile.
/// </summary>
public ICommand RenameCommand
{
    get
    {
        return _renameCommand ?? (_renameCommand = new RelayCommand<object>(obj =>
        {

            if(!(obj is object[] values)) return;


            if(!(values[0] is TextBox || values[0] is SetConfiguration) || !(values[1] is bool value)) return;

            if (values[0] is TextBox txtBox)
            {
                txtBox.IsReadOnly = value;
                if (!value)
                {
                    txtBox.Focus();
                    txtBox.SelectAll();
                }
            }

            if (values[0] is SetConfiguration config)
            {
                var listView = ListProfileView.ItemContainerGenerator.ContainerFromItem(config) as ListViewItem;
                var presenter = FindVisualChild<ContentPresenter>(listView);
                if(!(presenter.ContentTemplate.FindName("ProfileName", presenter) is TextBox txtBoxItem)) return;
                if (!value)
                {
                    txtBoxItem.Focus();
                    txtBoxItem.SelectAll();
                }
                txtBoxItem.IsReadOnly = value;
            }

        }));
    }
}

private static TChildItem FindVisualChild<TChildItem>(DependencyObject obj)
    where TChildItem : DependencyObject
{
    for (var i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
    {
        var child = VisualTreeHelper.GetChild(obj, i);
        if (child is TChildItem item)
            return item;

        var childOfChild = FindVisualChild<TChildItem>(child);
        if (childOfChild != null)
            return childOfChild;
    }
    return null;
}