【DevExpress】WPF DevExpressMVVM 24.1版本开发指南

DevExpressMVVM WPF 环境安装

前言

博主也是刚使用DevExpress所以哪里有问题和不足的地方欢迎大家提出来!

近一年博主都会做这个技术文档的更新,所以大家可以持续关注一下,如果完结的话博主会写出来!

优点:控件真是不错,各种集成,特别适合报表,工业,工具,系统等方向软件,功能强大,扩展丰富

缺点:开发文档全英文,对于英文不好的小伙伴不太友好,网络资源少(版本不一样解决方案不一样),导致开发需要摸索,建议搭配翻译软件看文档吧(官方网站有,自己去文档库下载)

重要Bug(必看)

1、在23.2和24.1都存在的一个bug,GridControl如果做动态更新,就不要使用DevExpressMvvm自带的[GenerateProperty]去声明数据源,而是采用ViewModel继承INotifyPropertyChanged的方式去声明,否则无法动态加载。

2、还是上述问题,但是!可能其他控件也存在相同的问题,情景是:AViewModel.cs去更新BViewModel.cs中的一个数据源,由于DevExpressMvvm没有依赖注入的技术支持,再加上博主没有使用Prism的事件聚合器,所以将BViewModel.cs定义为单例模式,通过A去调用B中的方法,从而改变B中的某个数据源,数据修改成功了,但是没有办法回显在画面上ItemsSourceChanged都没有触发,最后发现通过一个事件去调用方法则可以,例如画面定义一个Button然后绑定一个Click Command事件中就可以修改B中要改得数据源,具体原因不得而知!(由于这个问题博主已经将各个控件做成的UserControl合在一起了,等有时间在研究吧)

3、不确定是不是BUG,在GridControl中添加图片模板,模板内图片设置不上Source

csharp 复制代码
<Image Source="{Binding Url}" />

csharp 复制代码
<Image Source="{Binding Row.Url}" />

环境安装

直接官网安装https://www.evget.com/product/2346

建议使用同版本的,否则可能会有一点东西不一样,那探索起来简直是太棒了!

安装之后会有一个Demo Center 24.1 里面有大量Demo,但是进行了一部分DemoBase基类的集成,虽然不太影响阅读,但是阅读感不太好

控件目录

接下来是博主开发中涉及使用到的一些控件,有同样的小伙伴可以根据菜单直接进行查看!

Theme 主题

博主主要是要实现一种主题,不进行切换,但是根据网上资料设置好久都没成功,最后在文档中看到了预设主题的这么一个说明,上代码!

csharp 复制代码
using DevExpress.Xpf.Core;
using System.Windows;

namespace UIEditor
{
    /// <summary>
    /// Interaction logic for App.xaml
    /// </summary>
    public partial class App : Application
    {
        static App()
        {
            CompatibilitySettings.UseLightweightThemes = true;
        }

        protected override void OnStartup(StartupEventArgs e)
        {
            ApplicationThemeHelper.ApplicationThemeName = LightweightTheme.VS2019Light.Name;

            base.OnStartup(e);
        }
    }
}

主要需要重写OnStartup启动方法

LoginWindow 登陆窗口

博主做了一个登陆画面,思维一直锁在了导航里,其实对于Window也用不上导航,直接Show主界面,Close当前登录框就可以了,上代码!

LoginView.xaml

csharp 复制代码
<dx:ThemedWindow
    x:Class="UIEditor.Views.Login"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:ViewModels="clr-namespace:UIEditor.ViewModels"
    xmlns:dx="http://schemas.devexpress.com/winfx/2008/xaml/core"
    xmlns:dxe="http://schemas.devexpress.com/winfx/2008/xaml/editors"
    xmlns:dxmvvm="http://schemas.devexpress.com/winfx/2008/xaml/mvvm"
    xmlns:dxwui="http://schemas.devexpress.com/winfx/2008/xaml/windowsui"
    xmlns:dxwuin="http://schemas.devexpress.com/winfx/2008/xaml/windowsui/navigation"
    Title="Login"
    Width="280"
    Height="320">
    <dx:ThemedWindow.DataContext>
        <ViewModels:LoginViewModel />
    </dx:ThemedWindow.DataContext>
    <Grid VerticalAlignment="Center">
        <dxwui:NavigationFrame>
            <dxmvvm:Interaction.Behaviors>
                <dxwuin:FrameNavigationService />
            </dxmvvm:Interaction.Behaviors>
        </dxwui:NavigationFrame>
        <StackPanel>
            <TextBlock
                Margin="0,0,0,40"
                HorizontalAlignment="Center"
                Text="UI编辑器" />
            <TextBox Width="190" Margin="0,0,0,20" />
            <dxe:PasswordBoxEdit
                Width="190"
                Margin="0,0,0,20"
                VerticalAlignment="Center"
                NullText="请输入密码"
                NullValue="" />
            <Button
                Width="190"
                Command="{Binding LoginCommand}"
                Content="登陆" />
        </StackPanel>
    </Grid>
</dx:ThemedWindow>

注意dxwui,dxwuin是导航使用的,博主没删是等着下次用的时候直接过来粘贴大家不要管,直接看ButtonCommand="{Binding LoginCommand}"方法就可以了

LoginViewModel.cs

csharp 复制代码
 [GenerateCommand]
 public void Login()
 {
     MainWindow mainWindow = new MainWindow();
     mainWindow.Show();
     Application.Current.MainWindow.Close();
 }

大家应该注意到我只在ViewModel层声明了一个Login方法,DevExpress MVVM直接给我生成了一个LoginCommand,具体什么做法大家可以看一下底层,所以这也是DevExpress MVVM的强大之处,挺方便的

INavigationService 导航服务

由于DevExpress MVVM没提供 IoC依赖注入,博主也懒得弄了,所以在服务注入的时候需要换个方法,上代码!

XXXViewModel.cs

csharp 复制代码
private INavigationService NavigationService { get { return this.GetService<INavigationService>(); } }

[GenerateCommand]
public void Login()
{
    NavigationService.Navigate("MainWindow", null, this);
}

直接在ViewModel声明服务使用就可以了

XXXView.xaml

csharp 复制代码
  <dxwui:NavigationFrame>
            <dxmvvm:Interaction.Behaviors>
                <dxwuin:FrameNavigationService />
            </dxmvvm:Interaction.Behaviors>
        </dxwui:NavigationFrame>

需要粘一个这个用于导航就可以了(目前博主还没到真正使用导航的时候,如果有问题需要大家自己在研究一下)

DockLayout Dock类型的画面布局

博主要做一个仿照Vs2022的编译器所以会有控件布局拖拽的需求,于是看到DevExpree存在这个布局控件,于是使用了一下,实现简单,效果哇塞!上效果!

这样一个小窗口,可以按住控件的标题栏就行拖拽,与Vs2022一致,上代码!

csharp 复制代码
<dx:ThemedWindow
    x:Class="UIEditor.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:Views="clr-namespace:UIEditor.Views"
    xmlns:dx="http://schemas.devexpress.com/winfx/2008/xaml/core"
    xmlns:dxa="http://schemas.devexpress.com/winfx/2008/xaml/accordion"
    xmlns:dxdo="http://schemas.devexpress.com/winfx/2008/xaml/docking"
    xmlns:dxdove="http://schemas.devexpress.com/winfx/2008/xaml/docking/visualelements"
    xmlns:dxe="http://schemas.devexpress.com/winfx/2008/xaml/editors"
    xmlns:dxmvvm="http://schemas.devexpress.com/winfx/2008/xaml/mvvm"
    Title="UI编辑器"
    Width="1000"
    Height="800"
    dx:ThemeManager.ThemeName="VS2019Light"
    TitleAlignment="Center">
    <Grid>
        <dxdo:DockLayoutManager Margin="2">
            <dxdo:LayoutGroup>
                <dxdo:LayoutPanel
                    Caption="UI列表"
                    ItemWidth="*"                                                    //这个是用来做控件按百分比占宽度的
                    Visibility="Collapsed">
                    <Views:MainView />
                </dxdo:LayoutPanel>
                <dxdo:LayoutGroup ItemWidth="*" Orientation="Vertical">                     //这个是讲DockLayout中的内容在分为几块,主要用来初始化的时候展示布局的
                    <dxdo:LayoutPanel AllowClose="False" Caption="层级列表">
                        <Views:LevelView />
                    </dxdo:LayoutPanel>
                    <dxdo:LayoutPanel Caption="资源列表" Visibility="Collapsed">
                        <Views:UIListView />
                    </dxdo:LayoutPanel>
                </dxdo:LayoutGroup>
                <dxdo:LayoutPanel
                    Caption="Untiy程序"
                    ItemWidth="3.5*"
                    Visibility="Collapsed">
                    <Views:UIListView />
                </dxdo:LayoutPanel>
                <dxdo:LayoutPanel
                    AllowClose="False"                                   //禁止关闭模块了,有需要的可以删掉这个
                    Caption="属性区"
                    ItemWidth="3*">
                    <Views:PropertyView />
                </dxdo:LayoutPanel>
            </dxdo:LayoutGroup>
        </dxdo:DockLayoutManager>
    </Grid>
</dx:ThemedWindow>

TreeView 树状列表

博主要实现可以根据Type类型实现Icon不同,并且可修改Node节点名称的,大家先看一下代码吧!

注意引用类型的时候ImageSourcePresentationCore程序集的博主找了好久,一直告诉我Select返回类型不一致

XXXView.xaml

csharp 复制代码
<UserControl
    x:Class="UIEditor.Views.LevelView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:ViewModels="clr-namespace:UIEditor.ViewModels"
    xmlns:commn="clr-namespace:UIEditor.Common"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:dxdb="http://schemas.devexpress.com/winfx/2008/xaml/demobase/internal"
    xmlns:dxg="http://schemas.devexpress.com/winfx/2008/xaml/grid"
    xmlns:dxmvvm="http://schemas.devexpress.com/winfx/2008/xaml/mvvm"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    d:DesignHeight="300"
    d:DesignWidth="300"
    mc:Ignorable="d">
    <UserControl.DataContext>
        <ViewModels:LevelViewModel />
    </UserControl.DataContext>
    <UserControl.Resources>
        <commn:SolutionNodeImageSelector x:Key="_solutionNodeImageSelector" />
    </UserControl.Resources>
    <Grid>
        <dxg:TreeViewControl
            AllowEditing="True"
            AllowRecursiveNodeChecking="True"
            ChildNodesPath="Children"
            ItemsSource="{Binding Nodes}"
            NodeImageSelector="{StaticResource _solutionNodeImageSelector}"     // 节点图片选择器(根据英文名读的,英文菜鸡博主)主要是这个
            SearchPanelNullText="请输入名称进行检索"
            ShowBorder="False"
            ShowLoadingPanel="True"
            ShowNodeImages="True"
            ShowSearchPanel="True"
            TreeViewFieldName="Name" />
    </Grid>
</UserControl>

SolutionNodeImageSelector.cs

csharp 复制代码
using DevExpress.Xpf.Core;
using DevExpress.Xpf.Grid;
using DevExpress.Xpf.Grid.TreeList;
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Media;
using UIEditor.Models;
using UIEditor.Models.Enum;

namespace UIEditor.Common
{
    public class SolutionNodeImageSelector : TreeListNodeImageSelector
    {
        static SolutionNodeImageSelector()
        {
            ImageCache = new Dictionary<NodeType, ImageSource>();
        }
        static readonly Dictionary<NodeType, ImageSource> ImageCache;

        public override ImageSource Select(TreeListRowData rowData)
        {
            LevelNode solutionNode = (rowData.Row as LevelNode);

            if (solutionNode == null)
                return null;

            return GetImageByTypeNode(solutionNode.TypeNode);
        }

        public static ImageSource GetImageByTypeNode(NodeType typeNode)
        {
            if (ImageCache.ContainsKey(typeNode))
                return ImageCache[typeNode];
            var extension = new SvgImageSourceExtension() { Uri = new Uri(GetImagePathByTypeNode(typeNode)), Size = new Size(16, 16) };
            var image = (ImageSource)extension.ProvideValue(null);
            ImageCache.Add(typeNode, image);
            return image;
        }
        public static string GetImagePathByTypeNode(NodeType typeNode)
        {
            return "pack://application:,,,/UIEditor;component/Images/SolutionExplorer/" + typeNode.ToString() + ".svg";
        }
    }
}

这是模板代码,没什么可以说的,官方Demo给的,直接改一下GetImagePathByTypeNode返回的地址就好

最后是Model和Enum

Model.cs (也是官方Demo的)

csharp 复制代码
using System.Collections.Generic;
using UIEditor.Models.Enum;

namespace UIEditor.Models
{
    public class LevelNode {
        public NodeType TypeNode { get; set; }

        public string Name { get; set; }

        public string TypeName { get; set; }

        public string FileName { get; set; }

        List<LevelNode> _children;

        public IEnumerable<LevelNode> Children { get { return _children; } }

        public string DisplayName { get { return string.IsNullOrEmpty(TypeName) ? Name : string.Format("{0} : {1}", Name, TypeName); } }

        public string SearchString { get; set; }

        public string SearchName { get; set; }


        public void AddChildren(LevelNode child) {
            if(_children == null)
                _children = new List<LevelNode>();
            _children.Add(child);
        }
    }
}

Enum.cs (图片要和Enum同名,要不就在模板里写一下切换吧)

csharp 复制代码
/// <summary>
/// 层级树状类型字典
/// </summary>
public enum NodeType
{
    Image,
    Text,
    Color
}

注意:官方Demo有个坑,也是博主没看清,博主发现官方给的Demo初始化的时候,所有的节点都默认展开,最后发现例子里有这么一段

csharp 复制代码
Loaded="{DXEvent Handler='@Self.ExpandNode(0);@Self.ExpandNode(1)'}"

ExpandNode这个坑货,直接展开节点

GridControl 数据网格控件(等于DataGrid)

这个目前没什么需要注意的,可能就是实现跟DataGrid不太一样,博主是要在一个Cell添加多个图片,贴出来大家自己看一下吧,以这个为范例大家自由发挥

XXXView.xaml

csharp 复制代码
    <dxg:GridControl
     Grid.Row="1"
     ItemsSource="{Binding Properties, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
     ShowBorder="False">
     <dxg:GridColumn
         Width="80"
         FieldName="ActionCode"
         Header="动作编码" />
     <dxg:GridColumn
         Width="80"
         FieldName="Total"
         Header="最大数量"
         IsSmart="True" />
     <dxg:GridColumn
         Width="80"
         FieldName="Quantity"
         Header="实际数量"
         IsSmart="True" />
     <dxg:GridColumn
         Width="*"
         CellTemplate="{StaticResource _ImageTemplate}"
         Header="图片" />
     <dxg:GridControl.View>
         <dxg:TableView AllowPaging="True" />
     </dxg:GridControl.View>
 </dxg:GridControl>

_ImageTemplate(直接定义在xaml开头也行,定义一个模板文件也行)

csharp 复制代码
 <UserControl.Resources>
     <DataTemplate x:Key="_ImageTemplate">
         <StackPanel Orientation="Horizontal">
             <ItemsControl ItemsSource="{Binding Row.Images}">
                 <ItemsControl.ItemTemplate>
                     <DataTemplate>
                         <StackPanel>
                             <Image Source="{Binding Url}" />
                             <TextBlock Text="{Binding Size}" />
                             <TextBlock Text="{Binding Name}" />
                         </StackPanel>
                     </DataTemplate>
                 </ItemsControl.ItemTemplate>
                 <ItemsControl.ItemsPanel>
                     <ItemsPanelTemplate>
                         <WrapPanel />
                     </ItemsPanelTemplate>
                 </ItemsControl.ItemsPanel>
             </ItemsControl>
         </StackPanel>
     </DataTemplate>
 </UserControl.Resources>

ViewModel没什么东西无非就是定义一个数据源,弄两个实体,太多了就不贴出来了,有问题可以留言

注意:注意如果需要修改请使用FieldName的形式,如果用Binding绑定的话 无法用控件本身的修改

去掉标题栏上面的Seach按钮ShowGroupPanel="False",去掉左侧指示器ShowIndicator="False"

上个效果图:

LoadingDecorator 数据加载等待遮罩层

用于画面加载数据的等待效果,需要进行线程处理,防止UI线程阻塞,话不多说,上代码!

csharp 复制代码
<dx:LoadingDecorator IsSplashScreenShown="{Binding LoadingFlag}">
    <dx:LoadingDecorator.SplashScreenTemplate>                                                    // 如果不需要自定义东西 这层直接删掉,默认文字是Loading
        <DataTemplate>
            <dx:WaitIndicator Content="正在加载文件目录..." DeferedVisibility="True" />
        </DataTemplate>
    </dx:LoadingDecorator.SplashScreenTemplate>
	中间放处理逻辑
</dx:LoadingDecorator>

LoadingFlag就是一个bool值

如果不需要特殊效果SplashScreenTemplate这层就可以直接删掉,如果需要特殊处理,可以根据博主给出的代码,去文档里看,有官方Demo

在贴一个防止线程阻塞的方法

csharp 复制代码
  LoadingFlag = true;

  var files = await Task.Run(() =>
  {
      return this.GetPngImage(folderPath); // 这里面是耗时的逻辑处理
  });

  Properties = new ObservableCollection<PropertyModel>(files);

GridControl 分页方式

话不多说直接上代码!

csharp 复制代码
<dxg:GridControl>
    <dxg:GridControl.View>
        <dxg:TableView AllowPaging="True" />
    </dxg:GridControl.View>
</dxg:GridControl>

主要是AllowPaging这个属性就是是否开启分页

至于扩展自己去官方上看吧,链接: Devexpress WPF控件文档中心>>数据分页

FlowLayoutControl 流式布局(拖拽)

这个布局可以像StackPanel指定内部布局方式,主要是可以实现布局内控件"拖拽排序"功能,话不多说,上代码!

View.xaml

csharp 复制代码
<DataTemplate x:Key="_ImageTemplate">
    <dxlc:FlowLayoutControl
        AllowItemMoving="True"
        AnimateItemMoving="True"
        Background="White"
        ItemsSource="{Binding Row.Images}"
        Orientation="Horizontal"
        ShowLayerSeparators="True">
        <dxlc:FlowLayoutControl.ItemTemplate>
            <DataTemplate>
                <StackPanel Width="75" VerticalAlignment="Bottom">
                    <Image Source="{Binding Url}" />
                    <TextBlock
                        HorizontalAlignment="Center"
                        VerticalAlignment="Bottom"
                        Text="{Binding Size}" />
                    <TextBlock VerticalAlignment="Bottom" Text="{Binding Name}" />
                </StackPanel>
            </DataTemplate>
        </dxlc:FlowLayoutControl.ItemTemplate>
    </dxlc:FlowLayoutControl>
</DataTemplate>

这个没什么好说的,StackPanel层就是内容,可以自行修改,StackPanel可以不要,其他的扩展属性,请去文档或者官方查看

GridControl.ComboBox 数据网格中添加下拉列表

在GridControl中添加一个ComboBox 可以使用ComboBoxEditSettings,具体的上代码!

博主一开始进入一个误区,就是如何知道选择的是哪行的ComboBox值那,后来发现是套在GridColumn中的,只要GridColumn中的FieIdName和ComboBoxEditSettings的ValueMember是同类型值就可以联动修改...一开始人傻了找好久...

View.xaml

csharp 复制代码
xmlns:dxe="http://schemas.devexpress.com/winfx/2008/xaml/editors"
xmlns:dxg="http://schemas.devexpress.com/winfx/2008/xaml/grid"
  
<dxg:GridColumn
    Width="120"
    FieldName="ActionCode"
    Header="动作类型">
    <dxg:GridColumn.EditSettings>
        <dxe:ComboBoxEditSettings
            DisplayMember="Name"    // 指定显示字段
            IsTextEditable="False"
            ItemsSource="{Binding ActionTypes}"
            ValueMember="ActionCode" />   // 指定选中后的返回值
    </dxg:GridColumn.EditSettings>
</dxg:GridColumn>

ViewModel就不粘出来了,无非就是定义一个ActionTypes集合

Converter 显示转换

这个大家用的应该很多了,但是DevExpress提供了一个简便写法

View.xaml

csharp 复制代码
xmlns:dxmvvm="http://schemas.devexpress.com/winfx/2008/xaml/mvvm"

<UserControl.Resources>
    <dxmvvm:ObjectToObjectConverter x:Key="ActionCodeTypeConverter">
        <dxmvvm:MapItem Source="0" Target="待机,技能自身" />
        <dxmvvm:MapItem Source="1" Target="行走,技能过程" />
        <dxmvvm:MapItem Source="2" Target="跑步,技能目标" />
    </dxmvvm:ObjectToObjectConverter>
</UserControl.Resources>

<dxg:GridColumn
    Width="80"
    Binding="{Binding ActionCode, Converter={StaticResource ActionCodeTypeConverter}}"
    GroupIndex="0"
    Header="动作" />

这没什么好说的,很好理解!

GridControl.Group 数据网格分组

会有需要根据某个字段进行分组的情况,博主在网上查了好久,没有什么特别明确的内容,直到看到一位网友的资料,其实很简单,只需要在需要的Column上加一个GroupIndex就可以了

附上这位网友的文章: wpf devexpress 排序、分组、过滤数据

附上官方说明文档:ColumnBase.SortOrderSort DataGrouping

csharp 复制代码
<dxg:GridColumn
    Width="80"
    Binding="{Binding ActionCode, Converter={StaticResource ActionCodeTypeConverter}}"
    GroupIndex="0"               // 加上这个就可以了,如果有多个的话Index值根据优先级排就行
    Header="动作" />

上个效果图:

GridControl 滚动动画(丝滑)

在GridControl.View中的TableView中添加AllowScrollAnimation="True"

csharp 复制代码
dxg:GridControl.View>
    <dxg:TableView AllowEditing="True" AllowScrollAnimation="True" />
</dxg:GridControl.View>

DXDialog 弹窗(重点*)

使用场景:父画面调用ShowDialog调用Window弹窗,弹窗关闭时在将数据回传到父画面

MainViewModel.cs

csharp 复制代码
 var viewModel = new AddPropertyPopViewModel() { GroupConfigCount = int.Parse(GroupConfigCount.Trim()) }; // 子画面ViewModel
 var view = new AddPropertyPopView() { DataContext = viewModel }; // 子画面View

 var dialog = new DXDialog
 {
     WindowStartupLocation = System.Windows.WindowStartupLocation.CenterScreen,
     Title = "新增动作",
     Content = view,
     Width = 800,
     Height = 800
 };

 var result = dialog.ShowDialog();

 if (result == true)
 {
 	// 这里是自己的处理逻辑
     if (viewModel.ActionTypeItem != null)
     {
         foreach (var item in viewModel.Properties)
         {
             item.ActionCode = viewModel.ActionTypeItem.ActionCode;
         }
     }
     Properties.AddRange(viewModel.Properties);
 }

官方的例子不好用,首先是IDialogService没有初始化,即使用GetService也不行,解决方法:

在使用IDialogService的xaml中添加这段,其他Service同理

csharp 复制代码
<dxmvvvm:Interaction.Behaviors>
    <dx:DialogService DialogWindowStartupLocation="CenterOwner">
        <dx:DialogService.ViewTemplate>
            <DataTemplate>
                <Views:AddPropertyPopView />
            </DataTemplate>
        </dx:DialogService.ViewTemplate>
    </dx:DialogService>
</dxmvvvm:Interaction.Behaviors>

博主尝试使用了订阅事件的方法传值,不知道DevExpressMvvm底层做什么了,viewModel对象永远不是活动的ViewModel,所以获取不到更新的值

GridControl 内控件的Command事件

博主知道是添加RelativeSource={RelativeSource AncestorType={x:Type UserControl}来指向UserControl获取Command事件

但是就是不好用。

解决方法:

csharp 复制代码
Command="{Binding Path=DataContext.RemovPropertyCommand, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"

没错 加个Path=DataContext.

ScrollViewer 滚动条

csharp 复制代码
<ScrollViewer VerticalScrollBarVisibility="Auto">

</ScrollViewer>

没什么说的,把要带滚动条的控件放里面就可以了

持续更新中...

相关推荐
晚安苏州9 小时前
WPF DataTemplate 数据模板
wpf
甜甜不吃芥末1 天前
WPF依赖属性详解
wpf
Hat_man_1 天前
WPF制作图片闪烁的自定义控件
wpf
晚安苏州3 天前
WPF Binding 绑定
wpf·wpf binding·wpf 绑定
wangnaisheng3 天前
【WPF】RenderTargetBitmap的使用
wpf
dotent·3 天前
WPF 完美解决改变指示灯的颜色
wpf
orangapple5 天前
WPF 用Vlc.DotNet.Wpf实现视频播放、停止、暂停功能
wpf·音视频
ysdysyn5 天前
wpf mvvm 数据绑定数据(按钮文字表头都可以),根据长度进行换行,并把换行的文字居中
c#·wpf·mvvm
orangapple5 天前
WPF 使用LibVLCSharp.WPF实现视频播放、停止、暂停功能
wpf
晚安苏州5 天前
WPF ControlTemplate 控件模板
wpf