与主程序通信的 UserControl 事件

Posted

技术标签:

【中文标题】与主程序通信的 UserControl 事件【英文标题】:UserControl event to communicate with Main Program 【发布时间】:2016-10-25 15:33:50 【问题描述】:

我正在使用C#Blend 并正在开发WPF 应用程序。我正在尝试创建自己的控件来列出项目 - 例如文件名。我使用的是StackPanel,每个文件名都是自定义的UserControl。我所追求的是,​​当单击 StackPanel.Children 中的 UserControl 时,UserControl 会突出显示(预定义的视觉状态),而其他所有控件都不会突出显示。我在 UserControl 端代码中有 Click 事件,但我坚持尝试实现它以告诉所有其他 UserControls 变得不亮。我已经向 UserControl 添加了一个可公开访问的属性,名为 Selected,所以我想也许 Main 程序可以改变它,然后让 UserControl 代码检测到什么时候改变,并将它的视觉状态更改为未突出显示。它本质上是一个关闭多选的列表框!谢谢

编辑

private void UserControl_MouseDown(object sender, MouseButtonEventArgs e)
    
        if (Selected == false)
        
            VisualStateManager.GoToState(this, "Highlighted", true);
            Selected = true;
        

        else
        
            VisualStateManager.GoToState(this, "Normal", true);
            Selected = false;
        
    

【问题讨论】:

你能提供一些代码吗?太抽象了 它“本质上”不是单选列表框;它是一个带有 ItemTemplate 的单选 ListBox。不要重新发明所有这些***。 也许在 UserControl 中创建一个名为MainParent 的变量并从那里的主窗口调用一个函数? @EdPlunkett 我最初确实这样做了,尽管当您单击列表框项目时,您仍然会讨厌突出显示我不想要的项目! 您的整个问题都是关于突出显示该项目。您是说要以不同的方式突出显示它吗? 【参考方案1】:

这里快速演示了如何仅在 XAML 中完全更改列表框项的外观和行为。这里所有的 C# 代码只是定义和填充列表框中显示的集合。两个不同的列表框中有两个集合,以展示编写通用(一般通用,而不是Foo<T> 意义上的特定通用)项目容器模板的价值,该模板不知道您要放入什么样的内容。

确实,这是一本非常基础的入门书,让您了解如何看待 XAML/MVVM 中的 UI 编程。 MVVM 是一种不同的思维方式。就像 OOP 一样,它看起来是任意的,直到你摸索,然后它被认为是非常强大的。

如果您仔细考虑这段代码,直到您理解它所做的一切,您将迈出启蒙的第一步。所有这些代码都是死记硬背的,按数字绘制的东西。一旦你掌握了这些概念,将它们应用到任何远程正常的情况下都不会具有挑战性。这可以让您将脑细胞保存在真正具有挑战性的东西上。

首先,我们将编写一个包含喜欢、事物和东西的集合的快速视图模型。

ViewModels.cs

using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;

namespace CustomListBox

    public class ViewModelBase : INotifyPropertyChanged
    
        public event PropertyChangedEventHandler PropertyChanged;

        protected void OnPropertyChanged([CallerMemberName] String propName = null)
        
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
        
    

    public class ThingViewModel : ViewModelBase
    
        #region Name Property
        private String _name = "";
        public String Name
        
            get  return _name; 
            set
            
                if (value != _name)
                
                    _name = value;
                    OnPropertyChanged();
                
            
        
        #endregion Name Property
    

    public class MainViewModel : ViewModelBase
    
        #region Things Property
        private ObservableCollection<ThingViewModel> _things = new ObservableCollection<ThingViewModel>();
        public ObservableCollection<ThingViewModel> Things
        
            get  return _things; 
            set
            
                if (value != _things)
                
                    _things = value;
                    OnPropertyChanged();
                
            
        
        #endregion Things Property

        #region Stuff Property
        private ObservableCollection<object> _stuff = new ObservableCollection<object>();
        public ObservableCollection<object> Stuff
        
            get  return _stuff; 
            set
            
                if (value != _stuff)
                
                    _stuff = value;
                    OnPropertyChanged();
                
            
        
        #endregion Stuff Property
    

我们将在主窗口的代码隐藏构造函数中用一些任意的东西填充主视图模型的集合。

using System;
using System.IO;
using System.Linq;
using System.Windows;
using System.Collections.ObjectModel;

namespace CustomListBox

    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    
        public MainWindow()
        
            InitializeComponent();

            ViewModel.Things = new ObservableCollection<ThingViewModel>(
                Directory.GetFiles("c:\\").Select(fn => new ThingViewModel  Name = fn ));


            ViewModel.Stuff = new ObservableCollection<Object>(
                Enumerable.Range(1, 10).Select(n => new  Blah = Math.Log(n), Foobar = n ));

            ViewModel.Stuff.Insert(0, new  Blah = "Different type, same name", Foobar = "LOL" );
        

        public MainViewModel ViewModel => (MainViewModel)DataContext;
    

这是 XAML:

<Window 
    x:Class="CustomListBox.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:local="clr-namespace:CustomListBox"
    mc:Ignorable="d"
    Title="MainWindow" Height="400" Width="600">
    <Window.DataContext>
        <local:MainViewModel />
    </Window.DataContext>
    <Window.Resources>
        <Style x:Key="UglySelectionListBox" TargetType="ListBox" BasedOn="StaticResource x:Type ListBox">
            <Setter Property="ItemContainerStyle">
                <Setter.Value>
                    <Style TargetType="ListBoxItem" BasedOn="StaticResource x:Type ListBoxItem">
                        <!-- Need this so the whole listbox item is clickable -->
                        <Setter Property="IsHitTestVisible" Value="True" />
                        <Setter Property="Template">
                            <Setter.Value>
                                <ControlTemplate TargetType="ListBoxItem">
                                        <Border 
                                            x:Name="BackgroundBorder" 
                                            BorderThickness="2" 
                                            CornerRadius="4"
                                            Margin="0"
                                            Padding="4"
                                            Background="Transparent"
                                            BorderBrush="#01ffffff"
                                            >
                                            <Grid>
                                                <Grid.ColumnDefinitions>
                                                    <ColumnDefinition Width="Auto" />
                                                    <ColumnDefinition Width="*" />
                                                </Grid.ColumnDefinitions>
                                                <Path
                                                    x:Name="SelectionMark"
                                                    Stroke="Black"
                                                    StrokeThickness="2"
                                                    Data="M0,8 L 4,12 L 10,0"
                                                    Visibility="Hidden"
                                                    />
                                                <!-- 
                                                The ContentPresenter presents the Content property of the ListBoxItem.
                                                The ItemTemplate or DisplayMemberPath determine what the Content actually is
                                                -->
                                                <ContentPresenter ContentSource="Content" Grid.Column="1" />
                                            </Grid>
                                        </Border>
                                    <ControlTemplate.Triggers>
                                        <Trigger Property="IsMouseOver" Value="True">
                                            <Setter TargetName="BackgroundBorder" Property="BorderBrush" Value="Silver" />
                                            <Setter TargetName="BackgroundBorder" Property="Background" Value="Gainsboro" />
                                            <Setter TargetName="SelectionMark" Property="Stroke" Value="Silver" />
                                            <Setter TargetName="SelectionMark" Property="Visibility" Value="Visible" />
                                        </Trigger>
                                        <Trigger Property="IsSelected" Value="True">
                                            <Setter TargetName="BackgroundBorder" Property="Background" Value="LightSkyBlue" />
                                            <Setter TargetName="BackgroundBorder" Property="BorderBrush" Value="Red" />
                                            <Setter TargetName="BackgroundBorder" Property="TextElement.FontWeight" Value="Bold" />
                                            <Setter TargetName="SelectionMark" Property="Stroke" Value="Black" />
                                            <Setter TargetName="SelectionMark" Property="Visibility" Value="Visible" />
                                        </Trigger>
                                    </ControlTemplate.Triggers>
                                </ControlTemplate>
                            </Setter.Value>
                        </Setter>
                    </Style>
                </Setter.Value>
            </Setter>
        </Style>
    </Window.Resources>
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>

        <StackPanel 
            Grid.Column="0"
            Orientation="Vertical"
            >
            <!-- 
            ListBox.SelectedValue goes to the selected item, and returns the 
            value of the property named by SelectedValuePath 
            -->
            <TextBlock 
                Text="Binding SelectedValue, ElementName=StuffBox" 
                ToolTip="This displays the 'value' of the SelectedItem in the left listbox"
                />
            <ListBox 
                ItemsSource="Binding Stuff"
                Style="StaticResource UglySelectionListBox"
                DisplayMemberPath="Blah"
                SelectedValuePath="Foobar"
                Background="Beige"
                x:Name="StuffBox"
                />
        </StackPanel>
        <ListBox 
            Grid.Column="1"
            ItemsSource="Binding Things"
            Style="StaticResource UglySelectionListBox"
            >
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <TextBlock Text="Binding Name" />
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
    </Grid>
</Window>

好的。我们看到我们可以在ListBox 中显示各种东西。 ListBoxItem 模板决定了ListBoxItem 本身的外观和行为,除了内容之外的一切。然后我们可以根据我们在ListBox 中填充的内容,将不同类型的内容放入其中。我们可以命名一个要显示的属性(如在 StuffBox 中),或者提供一个 DataTemplate 项,它为我们提供了更多选项——您可以将 XAML 的所有子代都放在该模板中。它可以有自己的触发器。你喜欢的都可以。为 lolz 试试这个:

<DataTemplate>
    <StackPanel Orientation="Horizontal">
        <TextBlock Text="Binding Name" />
        <ListBox ItemsSource="Binding Name" />
    </StackPanel>
</DataTemplate>

String 实现IEnumerable&lt;T&gt;。这是一个字符序列。因此,您可以将其用作ItemsSourceListBox。你可以对这些东西发疯。

【讨论】:

感谢@EdPlunket,这看起来很棒。我试过了会回复你的! 感谢您。它对我的 XAML 有很大帮助。虽然我追求的是一个相当基于代码的解决方案。我知道如何在单击 UserControl 时更改它的 VisualState,但我不知道在同一个堆栈面板中单击另一个(相同)UserControl 时如何更改同一控件的 VisualState! @Char 玩得开心。

以上是关于与主程序通信的 UserControl 事件的主要内容,如果未能解决你的问题,请参考以下文章

如何在 MVVM 中的 UserControl 之间进行通信 - WPF 应用程序

UserControl KeyDown 事件不会触发撤销 Windows 应用程序 KeyDown 事件

Winform异步初始化UserControl的问题

C#UserControl委托和事件

“WPF UserControl.Unloaded”选项卡更改与卸载

未为 wpf 中相同类型的 UserControl 触发未加载事件