2024年4月

概要

本文将详细介绍如何使用
WPF
(Windows Presentation Foundation)开发一个分页控件,并深入解析其实现原理。我们将通过使用XAML和C#代码相结合的方式构建分页控件,并确保它具有高度的可定制性,以便在不同的应用场景中满足各种需求。

一.简介

分页控件是在许多应用程序中常见的一种界面元素,特别是在数据展示的场景中。它允许用户浏览大量数据,并根据需要切换到
不同的
数据页。

二.需求分析

我们首先来分析一下一个分页控件的基本构成。

2.1 总条目数(TotalItems)

表示总数据量。

2.2 每页条目数(PageSize)

表示每页显示的条目数。

2.3 总页数(PageCount)

表示根据总条目数与每页条目数计算出来的页数。

2.4 分页/页码按钮数量(PageNumberCount)

分页控件中可以点击的页码按钮。

2.5 当前页(CurrentPage)

当前显示的页,通常高亮显示。

三.控件命令和事件

3.1 页面跳转命令(GoToPageCommand)

该命令用于在XAML代码中触发页面跳转操作。

3.2当前页变更事件

当CurrentPage参数改变后触发该事件,通常在该事件中执行数据查询操作。

四.代码实现

通过以上原理分析,我们提取出了分页控件需要包含的基本要素,下面我们通过这些信息来组装成一个分页控件。

1 <StyleTargetType="{x:Type local:Pager}">
2     <SetterProperty="Template">
3         <Setter.Value>
4             <ControlTemplateTargetType="{x:Type local:Pager}">
5                 <BorderBackground="{TemplateBinding Background}"
6 BorderBrush="{TemplateBinding BorderBrush}"
7 BorderThickness="{TemplateBinding BorderThickness}">
8                     <StackPanelOrientation="Horizontal"ClipToBounds="True">
9                         <ButtonCommand="{x:Static local:Pager.GoToPageCommand}"CommandParameter="1"Margin="0,0,5,0"Content="首页"></Button>
10                         <ButtonCommand="{x:Static local:Pager.GoToPageCommand}"CommandParameter="-"Margin="5,0"Content="上一页"></Button>
11                         <ItemsControlItemsSource="{TemplateBinding PageButtons}">
12                             <ItemsControl.ItemsPanel>
13                                 <ItemsPanelTemplate>
14                                     <StackPanelOrientation="Horizontal"/>
15                                 </ItemsPanelTemplate>
16                             </ItemsControl.ItemsPanel>
17                             <ItemsControl.ItemTemplate>
18                                 <DataTemplate>
19                                     <ToggleButtonMinWidth="{Binding RelativeSource={RelativeSource Mode=Self},Path=ActualHeight}"              
20 Content="{Binding Page}"                        
21 IsChecked="{Binding IsCurrentPage}"                        
22 Command="{x:Static local:Pager.GoToPageCommand}"
23 CommandParameter="{Binding Page}"
24 Margin="5,0"/>
25                                 </DataTemplate>
26                             </ItemsControl.ItemTemplate>
27                         </ItemsControl>
28                         <ButtonCommand="{x:Static local:Pager.GoToPageCommand}"CommandParameter="+"Margin="5,0"Content="下一页"></Button>
29                         <ButtonCommand="{x:Static local:Pager.GoToPageCommand}"CommandParameter="尾页"Margin="5,0,0,0"Content="{DynamicResource LastPage}"></Button>
30                     </StackPanel>
31                 </Border>
32             </ControlTemplate>
33         </Setter.Value>
34     </Setter> 
35 </Style>

五.多样化需求

在不同的业务场景下需要的分页控件可能不尽相同,那么如何来满足多样化需求呢,答案就是自定义控件模板。下面演示几种常见的分页控件如何实现。

只需要“上一页”、“下一页”的情况

1 <StyleTargetType="{x:Type Controls:Pager}">
2     <SetterProperty="Template">
3         <Setter.Value>
4             <ControlTemplateTargetType="{x:Type Controls:Pager}">
5                 <BorderBackground="{TemplateBinding Background}"
6 BorderBrush="{TemplateBinding BorderBrush}"
7 BorderThickness="{TemplateBinding BorderThickness}">
8                     <StackPanelOrientation="Horizontal"ClipToBounds="True">
9                         <ButtonCommand="{x:Static Controls:Pager.GoToPageCommand}"CommandParameter="-"Margin="5,0"Content="{DynamicResource PreviousPage}"></Button>
10                         <ButtonCommand="{x:Static Controls:Pager.GoToPageCommand}"CommandParameter="+"Margin="5,0"Content="{DynamicResource NextPage}"></Button>
11                     </StackPanel>
12                 </Border>
13             </ControlTemplate>
14         </Setter.Value>
15     </Setter>
16 </Style>

只需要“首页”、“上一页”、“下一页”、“尾页”的情况。

1 <StyleTargetType="{x:Type Controls:Pager}">
2     <SetterProperty="Template">
3         <Setter.Value>
4             <ControlTemplateTargetType="{x:Type Controls:Pager}">
5                 <BorderBackground="{TemplateBinding Background}"
6 BorderBrush="{TemplateBinding BorderBrush}"
7 BorderThickness="{TemplateBinding BorderThickness}">
8                     <StackPanelOrientation="Horizontal"ClipToBounds="True">
9                         <ButtonCommand="{x:Static Controls:Pager.GoToPageCommand}"CommandParameter="1"Margin="0,0,5,0"Content="{DynamicResource FirstPage}"></Button>
10                         <ButtonCommand="{x:Static Controls:Pager.GoToPageCommand}"CommandParameter="-"Margin="5,0"Content="{DynamicResource PreviousPage}"></Button>
11                         <ButtonCommand="{x:Static Controls:Pager.GoToPageCommand}"CommandParameter="+"Margin="5,0"Content="{DynamicResource NextPage}"></Button>
12                         <ButtonCommand="{x:Static Controls:Pager.GoToPageCommand}"CommandParameter="{TemplateBinding PageCount}"Margin="5,0,0,0"Content="{DynamicResource LastPage}"></Button>
13                     </StackPanel>
14                 </Border>
15             </ControlTemplate>
16         </Setter.Value>
17     </Setter>
18 </Style>

数字显示“首页”、“尾页”的情况。

1 <StyleTargetType="{x:Type Controls:Pager}">
2     <SetterProperty="Template">
3         <Setter.Value>
4             <ControlTemplateTargetType="{x:Type Controls:Pager}">
5                 <BorderBackground="{TemplateBinding Background}"
6 BorderBrush="{TemplateBinding BorderBrush}"
7 BorderThickness="{TemplateBinding BorderThickness}">
8                     <StackPanelOrientation="Horizontal"ClipToBounds="True">
9                         <ButtonCommand="{x:Static Controls:Pager.GoToPageCommand}" 
10 CommandParameter="1" 
11 Content="1"
12 MinWidth="{Binding RelativeSource={RelativeSource Mode=Self},Path=ActualHeight}" 
13 Margin="0,0,5,0">
14                             <Button.Visibility>
15                                 <MultiBindingConverter="{StaticResource PageNumberToVisibilityConverter}"ConverterParameter="First">
16                                     <BindingRelativeSource="{RelativeSource AncestorType=Controls:Pager}"Path="CurrentPage"/>
17                                     <BindingRelativeSource="{RelativeSource AncestorType=Controls:Pager}"Path="PageNumberCount"/>
18                                     <BindingRelativeSource="{RelativeSource AncestorType=Controls:Pager}"Path="PageCount"/>
19                                 </MultiBinding>
20                             </Button.Visibility>
21                         </Button>
22                         <ButtonIsEnabled="False"Margin="5,0"MinWidth="{Binding RelativeSource={RelativeSource Mode=Self},Path=ActualHeight}"Content="...">
23                             <Button.Visibility>
24                                 <MultiBindingConverter="{StaticResource PageNumberToVisibilityConverter}"ConverterParameter="First">
25                                     <BindingRelativeSource="{RelativeSource AncestorType=Controls:Pager}"Path="CurrentPage"/>
26                                     <BindingRelativeSource="{RelativeSource AncestorType=Controls:Pager}"Path="PageNumberCount"/>
27                                     <BindingRelativeSource="{RelativeSource AncestorType=Controls:Pager}"Path="PageCount"/>
28                                 </MultiBinding>
29                             </Button.Visibility>
30                         </Button>
31                         <ButtonCommand="{x:Static Controls:Pager.GoToPageCommand}"CommandParameter="-"Margin="5,0"Content="{DynamicResource PreviousPage}"></Button>
32                         <ItemsControlItemsSource="{TemplateBinding PageButtons}">
33                             <ItemsControl.ItemsPanel>
34                                 <ItemsPanelTemplate>
35                                     <StackPanelOrientation="Horizontal"/>
36                                 </ItemsPanelTemplate>
37                             </ItemsControl.ItemsPanel>
38                             <ItemsControl.ItemTemplate>
39                                 <DataTemplate>
40                                     <ToggleButtonMinWidth="{Binding RelativeSource={RelativeSource Mode=Self},Path=ActualHeight}" 
41 Content="{Binding Page}" 
42 Margin="5,0" 
43 IsChecked="{Binding IsCurrentPage}"
44 Command="{x:Static Controls:Pager.GoToPageCommand}"
45 CommandParameter="{Binding Page}"/>
46                                 </DataTemplate>
47                             </ItemsControl.ItemTemplate>
48                         </ItemsControl>
49                         <ButtonCommand="{x:Static Controls:Pager.GoToPageCommand}"CommandParameter="+"Margin="5,0"Content="{DynamicResource NextPage}"></Button>
50                         <ButtonIsEnabled="False"Margin="5,0"MinWidth="{Binding RelativeSource={RelativeSource Mode=Self},Path=ActualHeight}"Content="...">
51                             <Button.Visibility>
52                                 <MultiBindingConverter="{StaticResource PageNumberToVisibilityConverter}">
53                                     <BindingRelativeSource="{RelativeSource AncestorType=Controls:Pager}"Path="CurrentPage"/>
54                                     <BindingRelativeSource="{RelativeSource AncestorType=Controls:Pager}"Path="PageNumberCount"/>
55                                     <BindingRelativeSource="{RelativeSource AncestorType=Controls:Pager}"Path="PageCount"/>
56                                 </MultiBinding>
57                             </Button.Visibility>
58                         </Button>
59                         <ButtonCommand="{x:Static Controls:Pager.GoToPageCommand}" 
60 CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=Controls:Pager},Path=PageCount}" 
61 Content="{Binding RelativeSource={RelativeSource AncestorType=Controls:Pager},Path=PageCount}" 
62 MinWidth="{Binding RelativeSource={RelativeSource Mode=Self},Path=ActualHeight}"
63 Margin="5,0,0,0">
64                             <Button.Visibility>
65                                 <MultiBindingConverter="{StaticResource PageNumberToVisibilityConverter}">
66                                     <BindingRelativeSource="{RelativeSource AncestorType=Controls:Pager}"Path="CurrentPage"/>
67                                     <BindingRelativeSource="{RelativeSource AncestorType=Controls:Pager}"Path="PageNumberCount"/>
68                                     <BindingRelativeSource="{RelativeSource AncestorType=Controls:Pager}"Path="PageCount"/>
69                                 </MultiBinding>
70                             </Button.Visibility>
71                         </Button>
72 
73                         <StackPanelOrientation="Horizontal"Margin="5,0,0,0">
74                             <TextBlockText="转到"VerticalAlignment="Center"/>
75                             <TextBoxx:Name="tbox_Page"Width="40"Margin="5,0"Padding="5"HorizontalContentAlignment="Center"VerticalAlignment="Center"/>
76                             <TextBlockText="页"VerticalAlignment="Center"/>
77                             <ButtonMargin="5,0,0,0"Command="{x:Static Controls:Pager.GoToPageCommand}"CommandParameter="{Binding ElementName=tbox_Page,Path=Text}">确定</Button>
78                         </StackPanel>
79                     </StackPanel>
80                 </Border>
81             </ControlTemplate>
82         </Setter.Value>
83     </Setter>
84 </Style>

可以调整每页显示条目,可以显示总页数,可以跳转到指定页的情况。

1 <StyleTargetType="{x:Type Controls:Pager}">
2     <SetterProperty="Template">
3         <Setter.Value>
4             <ControlTemplateTargetType="{x:Type Controls:Pager}">
5                 <BorderBackground="{TemplateBinding Background}"
6 BorderBrush="{TemplateBinding BorderBrush}"
7 BorderThickness="{TemplateBinding BorderThickness}">
8                     <StackPanelOrientation="Horizontal"ClipToBounds="True">
9                         <ButtonCommand="{x:Static Controls:Pager.GoToPageCommand}"CommandParameter="1"Margin="0,0,5,0"MinWidth="{Binding RelativeSource={RelativeSource Mode=Self},Path=ActualHeight}"Content="{DynamicResource FirstPage}"></Button>
10                         <ButtonCommand="{x:Static Controls:Pager.GoToPageCommand}"CommandParameter="-"Margin="5,0"MinWidth="{Binding RelativeSource={RelativeSource Mode=Self},Path=ActualHeight}"Content="{DynamicResource PreviousPage}"></Button>
11                         <ItemsControlItemsSource="{TemplateBinding PageButtons}">
12                             <ItemsControl.ItemsPanel>
13                                 <ItemsPanelTemplate>
14                                     <StackPanelOrientation="Horizontal"/>
15                                 </ItemsPanelTemplate>
16                             </ItemsControl.ItemsPanel>
17                             <ItemsControl.ItemTemplate>
18                                 <DataTemplate>
19                                     <ToggleButtonMinWidth="{Binding RelativeSource={RelativeSource Mode=Self},Path=ActualHeight}" 
20 Content="{Binding Page}" 
21 Margin="5,0" 
22 IsChecked="{Binding IsCurrentPage}"
23 Command="{x:Static Controls:Pager.GoToPageCommand}"
24 CommandParameter="{Binding Page}"/>
25                                 </DataTemplate>
26                             </ItemsControl.ItemTemplate>
27                         </ItemsControl>
28                         <ButtonCommand="{x:Static Controls:Pager.GoToPageCommand}"CommandParameter="+"Margin="5,0"MinWidth="{Binding RelativeSource={RelativeSource Mode=Self},Path=ActualHeight}"Content="{DynamicResource NextPage}"></Button>
29                         <ButtonCommand="{x:Static Controls:Pager.GoToPageCommand}"CommandParameter="{TemplateBinding PageCount}"Margin="5,0,0,0"MinWidth="{Binding RelativeSource={RelativeSource Mode=Self},Path=ActualHeight}"Content="{DynamicResource LastPage}"></Button>
30                         <StackPanelOrientation="Horizontal"Margin="5,0,0,0">
31                             <TextBlockMargin="0,0,5,0"Text="每页"VerticalAlignment="Center"/>
32                             <ComboBoxMargin="5,0"SelectedIndex="0"VerticalContentAlignment="Center"SelectedItem="{Binding RelativeSource={RelativeSource AncestorType=Controls:Pager},Path=PageSize,Mode=OneWayToSource}">
33                                 <sys:Int32>5</sys:Int32>
34                                 <sys:Int32>10</sys:Int32>
35                                 <sys:Int32>15</sys:Int32>
36                                 <sys:Int32>20</sys:Int32>
37                             </ComboBox>
38                             <TextBlockText="条"VerticalAlignment="Center"Margin="5,0"/>
39                             <TextBlockVerticalAlignment="Center"Margin="5,0">
40                                         <RunText="共"/>
41                                         <RunText="{Binding RelativeSource={RelativeSource AncestorType=Controls:Pager},Path=PageCount}"/>
42                                         <RunText="页"/>
43                             </TextBlock>
44                             <TextBlockText="转到"VerticalAlignment="Center"/>
45                             <TextBoxx:Name="tbox_Page"Width="40"Margin="5,0"Padding="5"HorizontalContentAlignment="Center"VerticalAlignment="Center"/>
46                             <TextBlockText="页"VerticalAlignment="Center"/>
47                             <ButtonMargin="5,0,0,0"Command="{x:Static Controls:Pager.GoToPageCommand}"CommandParameter="{Binding ElementName=tbox_Page,Path=Text}">确定</Button>
48                         </StackPanel>
49                     </StackPanel>
50                 </Border>
51             </ControlTemplate>
52         </Setter.Value>
53     </Setter>
54 </Style>

除了修改模板实现不同的形态的分页控件以外,还可以用图标替换掉“首页”、“上一页”、“下一页”、”尾页”等文字。

六.个性化控件外观

项目中的界面外观可能多种多样,有自己写的控件样式,也有使用第三方UI库的样式,如何跟它们搭配使用呢。

自定义控件样式

1 <StyleTargetType="Button">
2     <SetterProperty="Padding"Value="5"/>
3     <SetterProperty="Background"Value="Red"/>
4 </Style>
5 <StyleTargetType="ToggleButton">
6     <SetterProperty="Padding"Value="5"/>
7     <SetterProperty="Background"Value="Red"/>
8 </Style>

使用第三方UI库

1.HandyControl

1 <ResourceDictionary>
2     <ResourceDictionary.MergedDictionaries>
3         <ResourceDictionarySource="pack://application:,,,/HandyControl;component/Themes/SkinDefault.xaml" />
4         <ResourceDictionarySource="pack://application:,,,/HandyControl;component/Themes/Theme.xaml" />
5     </ResourceDictionary.MergedDictionaries>
6 </ResourceDictionary>

2.MaterialDesign

1 <ResourceDictionary>
2     <ResourceDictionary.MergedDictionaries>
3         <materialDesign:CustomColorThemeBaseTheme="Light"PrimaryColor="#FF3561FF"SecondaryColor="#FF3561FF" />
4         <ResourceDictionarySource="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.Defaults.xaml" />
5     </ResourceDictionary.MergedDictionaries>
6 </ResourceDictionary>

技术交流群
联系方式

在Java8及更高版本中,Lambda表达式的引入极大地提升了编程的简洁性和效率。本文将围绕十个关键场景,展示Lambda如何助力提升开发效率,让代码更加精炼且易于理解。

集合遍历

传统的for-each循环对集合进行遍历虽然直观,但在处理大量数据时显得冗长。例如:

List<String> list = Arrays.asList("a", "b", "c");
for (String s : list) {
    System.out.println(s);
}

使用Lambda表达式后,代码变得更加紧凑:

list.forEach(System.out::println);

集合排序

在以前我们对集合中的元素进行排序时,需要实现
Comparable
接口,或者使用
Comparator
比较器,在其中定义排序规则。

Collections.sort(list, new Comparator<String>() {
    @Override
    public int compare(String s1, String s2) {
        return s1.length() - s2.length();
    }
});

使用Lambda可以进行简化:

List<String> sortedList = list.sort(Comparator.comparingInt(String::length));

// 或者
Collections.sort(list, (s1, s2) -> s1.length() - s2.length());

// 或者
Collections.sort(list, Comparator.comparingInt(String::length));

集合过滤

以往的过滤操作以往需要编写繁琐的条件判断。

List<String> filterList = new ArrayList<>();
for (String s : list){
	if (s.length() >= 4){
		filterList.add(s);
	}
}

使用Lambda可以进行简化:

List<String> filterList = list.stream().filter(e -> e.length() >= 4).collect(Collectors.toList());

关于Stream的使用方法请参考:
提高Java开发生产力,我选Stream,真香啊

映射操作

如以下操作,将一个集合变成另外一个集合

List<String> upperCaseList = new ArrayList<>();
for (String str : words) {
    upperCaseList.add(str.toUpperCase());
}

而Lambda表达式可用于将集合中的元素直接转换成新的形式:

List<String> upperList = list.stream().map(e -> e.toUpperCase()).collect(Collectors.toList());
		upperList = list.stream().map(String::toUpperCase).collect(Collectors.toList());
		
		List<Integer> lengthList = list.stream().map(e -> e.length()).collect(Collectors.toList());
		lengthList = list.stream().map(String::length).collect(Collectors.toList());

规约操作

规约操作,即对一个集合中的元素进行求和,求平均数等

int sum = 0;
for (int num : numbers) {
    sum += num;
}

使用Lambda简化

int sum = numbers.stream().mapToInt(Integer::intValue).sum();
int sum = numbers.stream().reduce(0, (n1, n2) -> n1 + n2);
int sum = numbers.stream().reduce(0, Integr::sum);

List<Person> peoples = new ArrayList<>();
int ages = peoples.stream().mapToInt(Person::getAge).sum();

关于Stream的使用方法请参考:
提高Java开发生产力,我选Stream,真香啊

分组操作

对一个集合基于特定规则对集合进行分组,即将
List<Object>
转换为
Map<Object, List<Object>>

List<Person> personList = new ArrayList<>();
Map<String, List<Person>> groupMap = new HashMap<>();
for (Person person : personList) {
    Integer age = person.getAge();
    if (!groupMap.containsKey(age)) {
        groupMap.put(age, new ArrayList<>());
    }
    groupMap.get(age).add(person);
}

使用Lambda简化:

Map<String, List<Person>> groupMap = words.stream()
    .collect(Collectors.groupingBy(Person::age));

还有另外一种
List<Object>
转换为
Map<Object, Object>
:

List<Person> personList = new ArrayList<>();
Map<Long, Person> personMap = new HashMap<>();
for (Person person : personList) {
   personMap.put(person.getId(), person);
}

使用Lambda简化:

Map<String, Person> groupMap = words.stream()
    .collect(Collectors.toMap(Person::id, Function.identity(), (e1, e2) -> e1));

关于Stream的使用方法请参考:
提高Java开发生产力,我选Stream,真香啊

使用函数式接口

现在有一个函数式接口:

@FunctionalInterface
interface MyInterface{
	void doSomething(String s);
}

常规做法在使用函数式接口时:

MyInterface myInterface = new MyInterface() {
	@Override
	public void doSomething(String s) {
		System.out.println(s);
	}
};

myInterface.doSomething("I am 码农Academy");

使用Lamba进行优化:

MyInterface myInterface = s -> System.out.println(s);
myInterface.doSomething("I am 码农Academy");

线程创建

以往创建线程的方式:

Thread thread = new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("Hello, 码农Academy!");
    }
});

使用Lambda简化后:

Thread thread = new Thread(() -> System.out.println("Hello, 码农Academy!"));

// 或者使用线程池方式
ExecutorService executor = Executors.newFixedThreadPool(5); executor.execute(() -> longRunningTask());

Optional

Optional可以避免空指针异常。

Optional<String> optional = ...;
if (optional.isPresent()) {
    String value = optional.get();
    // 处理value
}

使用Lambda简化:

Optional<String> optional = ...;
optional.ifPresent(value -> handleValue(value));

关于使用Optional解决空指针的用法,可以参考:
聊一聊日常开发中如何避免那无处不在的让人头疼的NullPointerException

Stream的流水操作

在处理业务时,我们需要对一个集合进行一系列的操作时,比如如下:
`

List<Integer> result = new ArrayList<>();
for (String str : list) {
    if (str.matches("\\d+")) {
        result.add(Integer.parseInt(str));
    }
}

利用Stream API与Lambda结合,实现链式操作,使代码更清晰易读:

List<Integer> result = list.stream()
    .filter(str -> str.matches("\\d+"))
    .map(Integer::parseInt)
    .collect(Collectors.toList());

比如我们使用Lambda结合Stream实现一个去重操作:

/**
     * 根据学生姓名查询除重复元素
     * @param students
     */
    private static void repeatStudentsTest(List<Student> students){
        // list 对应的 Stream
      List<String> repeatStudents =   students.stream()
              // 获得元素出现频率的 Map,键为元素,值为元素出现的次数
                .collect(Collectors.toMap(e -> e.getName(), e -> 1, Integer::sum))
              // 所有 entry 对应的 Stream
                .entrySet().stream()
              // 过滤出元素出现次数大于 1 的 entry(过滤出来的是重复的,若这里条件是等于,即可达到去重的目的)
                .filter(entry -> entry.getValue()>1)
            // 获得 entry 的键(重复元素)对应的 Stream
                .map(entry -> entry.getKey())
              // 转化为 List
                .collect(Collectors.toList());

        repeatStudents.forEach(repeatStudent -> {
            System.out.println(repeatStudent);
        });
    }

Lambda的断点调试

关于使用Idea开发式,以前对代码断点时确实无法进入到lamda表达式里面,但是随着Idea的升级,已经解决了这个问题,可以在Lambda表达式的内部进行断点

image.png

image.png

Lambda易读

有人可能会认为Lambda表达式的代码阅读起来有些吃力,当然也是可以理解,其主要原因有如下几个方面:

  1. 匿名性
    :Lambda表达式本质上是匿名函数,没有显式的方法名称,因此,初次接触或不熟悉其语法的读者可能难以快速理解其意图,尤其是在较复杂的上下文中。

  2. 简洁性
    :Lambda表达式的目的是为了简化代码,它往往非常紧凑,可能会把原本分散在多个行或方法中的逻辑压缩到一行甚至一部分内。这样的代码密度可能导致理解上的难度,特别是当逻辑较为复杂时。

  3. 抽象层次
    :Lambda表达式常与函数式接口一起使用,这意味着理解Lambda表达式需要知道它所对应接口的行为约定。如果读者不了解接口的具体功能,那么Lambda表达式就可能变得难以解读。

  4. 函数式编程范式
    :对于习惯于命令式编程风格的开发者来说,函数式编程的思维方式和Lambda表达式的使用可能需要一定适应期。尤其是涉及到闭包、高阶函数等概念时,如果不熟悉这些概念,理解Lambda表达式的逻辑会更加困难。

  5. 依赖上下文
    :Lambda表达式经常用于流(Stream)操作、事件监听、回调函数等场景,其含义高度依赖于上下文环境。在缺少充分注释或文档的情况下,阅读者可能需要花费更多精力去推理其作用。

但是,随着Java 8以来函数式编程特性的普及,越来越多的Coder们开始接受并熟练使用Lambda表达式。适当的代码组织、注释和遵循良好的编程规范有助于降低Lambda表达式带来的阅读障碍。并且随着经验的增长和技术背景的丰富,我们会逐渐认识到Lambda表达式的优点,即它可以增强代码的可读性和简洁性,尤其在处理数据流和进行函数组合时。

总结

熟练运用Lambda表达式能够显著提升代码质量与开发效率,使得代码逻辑更加简明扼要,同时也增强了程序的可读性与维护性。不断学习和实践这些技巧,你的开发效率必将迎来质的飞跃。并且Lambda与Stream一起使用才能发挥他们最大的优点。关于Stream的使用方法请参考:
提高Java开发生产力,我选Stream,真香啊

本文已收录于我的个人博客:
码农Academy的博客,专注分享Java技术干货,包括Java基础、Spring Boot、Spring Cloud、Mysql、Redis、Elasticsearch、中间件、架构设计、面试题、程序员攻略等

3月29日,微软公司的开发人员 Andres Freund 在调查 SSH 性能问题时,发现了 xz 软件包中一个涉及混淆恶意代码的供应链攻击。进一步溯源发现 SSH 使用的上游 liblzma 库被植入了后门代码,恶意代码可能允许攻击者通过后门版本的 SSH 非授权获取系统的访问权限。恶意代码修改了 liblzma 代码中的函数,该代码是 XZ Utils 软件包的一部分,链接到 XZ 库的任何软件都可以使用此修改后的代码,并允许拦截和修改与该库一起使用的数据。

在互联网的世界里,有这样一群默默无闻的英雄,他们凭借着对技术的热爱和对开源事业的执着,为我们日常使用的软件默默贡献着自己的力量。
这些人就是开源软件的维护者。

今天这篇文章要从 Lasse Collin 开始讲起。Lasse 是 xz/liblzma 项目的维护者。这是个不起眼的小项目,但对于 Lasse 来说,它承载了自己多年的心血。

XZ 是一种通用的数据压缩格式,几乎存在于每个 Linux 发行版中。XZ Utils 是一个命令行工具,包含 XZ 文件和 liblzma 的压缩和解压缩功能,而 liblzma 是一种用于数据压缩的类似 zlib 的 API,并且还支持旧版。lzma 格式。

作为一名兼职的开源贡献者,Lasse 需要在有限的业余时间里处理各种琐碎的维护工作:修复 bug、开发新功能、回复用户问题,他几乎是一个人撑起了整个项目。虽然工作量不小,但看到项目在自己手中一点点成长、完善,Lasse 还是感到无比欣慰。

然而,现实的压力开始向 Lasse 袭来。长期高强度的义务劳动让他身心俱疲,再加上现实生活的重重困扰,Lasse 感到自己已经到了极限。“也许,是时候找个接班人了。” 他无奈地在社区里发出了这样的呼声。

就在这时,
一个名叫 Jia Tan 的开发者出现在 Lasse 面前,表示愿意帮助维护项目
。Jia Tan 在私下里也给了 Lasse 不少支持和鼓励。“太好了,终于有人能分担一些工作了。” Lasse 心里的石头落了地。

可好景不长,一些 “索取党” 用户开始对 Lasse 发起了猛烈的攻势:

文档怎么还不更新?

这个 bug 都过去一周了,什么时候才能修?

新功能开发太慢了,你行不行啊?

面对铺天盖地的质疑和要求,Lasse 感到无比委屈:“大家要理解,我也有自己的本职工作和生活啊,
这个项目只是我的业余爱好
。”

在 “索取党” 的不断締逼下,Lasse 的维护热情迅速消磨殆尽。他开始怀疑自己是否真的能胜任这个角色。正当 Lasse 处于人生的低谷时,Jia Tan 再次向他伸出了 “援手”:“
不如把项目交给我吧,我会好好打理的。

最终,在内忧外患的双重打击下,Lasse 选择了放手。
Jia Tan 如愿接管了 xz/liblzma 项目
。至此,这似乎是一个皆大欢喜的结局:Lasse 卸下了沉重的包袱,Jia Tan 得到了梦寐以求的机会,用户们也有了新的维护者。

然而,悲剧的一幕出现了。
Jia Tan 在掌控项目后不久,竟然在代码中植入了一个严重的安全漏洞
!这一切,原来都是精心策划的阴谋。而那些不断对 Lasse 施压的 “索取党”,无疑也要背负一定的责任。

这里不仅有技术层面的漏洞,还有人性的弱点。这位 Jia Tan 通过细致入微的
社会工程学
手法,攻击者一步步骗取了原维护者的信任:

首先,他通过两年来高质量的代码贡献,塑造了技术过硬、品行端正的正面形象,消除了他人的戒心。

接着,他敏锐地察觉到原维护者正处于倦怠期,主动提出分担工作,给予支持和鼓励,借机拉近关系。

同时,他还利用其他用户对原维护者的不满情绪,暗示自己可以接替其位置,渐渐把持项目。

这一环扣一环的心理操纵过程,正是社会工程学的典型应用。

当攻击者试图说服其他维护者采用有问题的代码库时,他可能使用了以下话术和策略:反复强调新版本的诸多 “优秀特性”,引诱对方上钩;对提出质疑的人轻描淡写,让对方觉得是自己多虑了;表现出真诚、耐心、乐于助人的一面,让人难以生疑。

这是社会工程学中 “制造权威”、“利用从众心理” 等常见手法。

这操作手法,是不是和那位在百度贴吧潜伏5年只是为了复仇的勇士很像?

更牛逼的是,这位兄台的 ID 也很讲究:
Jia Tan

这像什么?很明显是中国人名字的汉语拼音啊!心机太深了,顺便还黑了一把中国人。

目前 GitHub 官方已经将 xz 仓库给封了

Lasse 的悲剧,只是开源江湖中维护者群体的一个缩影。
他们默默无闻却身负重任,常常要独自面对来自方方面面的压力
:用户的苛责、生活的重担、项目的困境......在这些重压之下,即便是最有激情的维护者,也难免会心力交瘁。正是他们的付出,才撑起了整个开源软件的生态,但他们的处境却常常被忽视。

反思这个故事,我们不难发现一些让人警醒的细节。首先,
“索取党” 用户的不当言行,往往会成为压垮维护者的最后一根稻草
。诚然,提需求本身并无不妥,但过分苛刻、不近人情的要求只会适得其反。其次,项目的安全问题不容小觑。当维护者精疲力竭时,往往就是不法分子伺机而动的时候。最后,开源生态的良性发展,离不开所有参与者的共同努力。用户要学会体谅和支持,企业要主动承担社会责任,而平台方更要为维护者提供必要的保障措施。

Lasse 的故事还在继续,无数像他一样的开源英雄仍在默默耕耘。他们或许没有鲜花和掌声,但正是有了他们的奉献,我们才能享受到如此丰富多彩的开源世界。
作为开源社区的一员,我们应该心存感激,多一些理解和包容,少一些指责和催促

Hacker News 讨论帖:
https://news.ycombinator.com/item?id=39865810

1.安装环境vs2022+QT5.14.2

qt vs tool (vsaddin)的使用遇到的坑

1.安装qt-vsaddin-msvc2022-3.0.2.vsix 安装失败

2.安装qt-vsaddin-msvc2022-2.8.0.vsix 在qtSetting->qt models模块管理中,没有Select model的功能选项如下图位置

3.卸载版本vsaddin_2.8.0后安装qt-vsaddin-msvc2022-2.10.0.vsix,SelectModels按钮出现。

2.模块管理工具的作用

字面意思,主要是管理QT模块的加载和使用

使用不需要手动去vs包含目录添加.h文件目录,和连接器里面添加.lib文件,能自动调用Debug模式和Release模式下的库,不需要手动进行配置

手动配置qt环境遇到的问题

描述:1.创建完QT项目,在模块选择界面我只选择了默认的
Core,GUI,Widget模块
,后来想
使用QT的其他库QAxObject进行EXcel读写
,这个时候如果不能用
Select Models

出现问题:1.编译找不到对应模块的头文件

解决办法
:首先我们找到安装QT的位置 可以看到QT环境的大致情况,QT库相关的头文件都在include中,手动添加该路径到项目中

在项目设置-》附加包含目录 可以添加我们的头文件

继续编译,出现如下情况,
这里是找不到.lib文件了

手动配置库文件(.lib)
qaxobject模块需求Release(QT5AxBase.lib 和QT5AxContainer.lib) Debug(QT5AxBased.lib 和QT5AxContainerd.lib)

第一次添加,没有区分Debug和Release的库,这理Debug模式我添加了后缀不带d的QT5AxContainer.lib(Release版本的库),编译失败

修改为Debug适配库

编译成功

结论:QT Model Select管理使用模块很方便,当然后面手动添加是让我们了解下VS配置库的方法

本文分享自华为云社区《
GaussDB DWS的SQL ON ANYWHERE技术解密
》,作者:tooooooooooomy。

1. 前言

  • 适用版本:【8.1.1(及以上)】

查询分析是大数据要解决的核心问题之一,虽然大数据相关的处理引擎组件种类繁多,并提供了丰富的接口供用户使用,但相对传统数据库用户来说,SQL语言依然是使用最简单、最广泛和方便的一种接口。如果能在一个客户端中使用SQL语句操作不同的大数据组件,将极大提升使用各种大数据组件的效率。

2. 什么是SQL On Anywhere

GaussDB(DWS)的SQL On Anywhere,主要指对大数据的文件系统和与其他异构数据库的访问和交互,构筑起统一的大数据计算平台。大数据文件系统主要包括HDFS和OBS,其他异构数据库主要包括Oracle、Spark和Other GaussDB(DWS)。

3. GaussDB(DWS)SQL On Anywhere的作用及其应用场景

通过SQL On Anywhere特性可以实现与其他大数据组件和数据库互联互通访问,可以直接同时处理本地和HDFS/OBS上的数据集,甚至其他异构数据库的数据,而无需导入导出数据,将其分析能力从本地存储扩展到数据湖中,扩大GaussDB DWS的大数据分析的应用场景;通过该特性可以帮助客户实现冷热数据分离,将使用频度更高的热数据存储在本地,而使用频度更低的冷数据存储在成本更低廉的共享存储HDFS或者DWS上,降低用户成本。

img

从应用场景来看,可以满足如下业务需求:

  • 针对多数据源需要构建虚拟的统一数据仓库,实现多数据源联邦查询,跨数据仓库热数据和HDFS/OBS冷数据的复杂混合查询,需要提供一致的、熟悉的数据仓库操作体验。
  • 满足低频的业务全数据的低成本低延迟即席查询。

4. GaussDB(DWS)SQL On Anywhere的实现方式

GaussDB(DWS)SQL On Anywhere针对大数据的文件系统的访问主要通过FDW或ELK机制(已停止演进)实现的,而跨数据库的访问主要通过EC+ODBC的方式实现的。

img

3.1 利用FDW访问HDFS/OBS数据

GaussDB(DWS)对存储在HDFS上的Hadoop或者OBS原生数据的访问,采用FDW(Foreign Data Wrapper)机制,也称外表机制。首先通过创建Foreign Data Server来定义对HDFS数据源或同构其他集群的连接信息;之后创建Foreign Table,用于在GaussDB A数据库内部系统表中,定义对应的HDFS数据源上Hadoop原生结构化数据表的结构或对应同构其他集群结构化数据表的结构。

例如读取hdfs上的数据,其流程如下:

​ 1)建立一个hdfs_server,其中hdfs_fdw为数据库中存在的foreign data wrapper。

--创建hdfs_server。
postgres
=# CREATE SERVER hdfs_server FOREIGN DATA WRAPPER HDFS_FDW OPTIONS
(address
'10.146.187.231:8000,10.180.157.130:8000',
hdfscfgpath
'/opt/hadoop_client/HDFS/hadoop/etc/hadoop',
type
'HDFS') ;

​ 2)创建一个hdfs外表读取hdfs上的数据

CREATE FOREIGN TABLE region (
R_REGIONKEY INT4,
R_NAME TEXT,
R_COMMENT TEXT )
SERVER hdfs_server
OPTIONS(
FORMAT
'orc',
FOLDERNAME
'/user/hive/warehouse/mppdb.db/region_orc11_64stripe/')
DISTRIBUTE BY roundrobin;

​ 3)查询HDFS外表,例如:

select * from region limit 10;

目前外表支持与普通表进行关联查询,并支持多种文件存储格式,其支持的文件格式如下(不同版本能力可能存在差异,以官方文档为准):

3.2 通过ELK访问HDFS(已停止演进,不推荐)

ELK的方式类似于HAWQ,它是通过建立表空间为HDFS表空间,直接将数据存储和访问HDFS文件系统,目前只支持访问HDFS文件系统,而不支持访问OBS上的数据。首先通过创建HDFS表空间,然后会创建一个HDFS表,在创建时指定表空间为HDFS表空间,最后对HDFS表的操作如同普通表的操作,可进行插入修改删除数据。

以GaussDB数据库数据推到HDFS中

​ 1)在数据库中创建HDFS表空间

CREATE TABLESPACE hdfs_table RELATIVE LOCATION ‘tmp/hdtest’
With (filesystem
=’hdfs’,
address
=’28.4.136.221:9000’,
cfgpath
=’/opt/Huawei/bigdata/mppdb/hdfs_conf/zhndnrop/omm@HADOOP.COM/’,
storepath
=’/tmp/test’);

​ 2)数据库中创建HDFS表

CREATE TABLE abc(
zjxxlh
char(20),
nbbsh
char(20),
khwybh
char(20),
zjlx
char(20)
)WITH (orientation
=orc) TABLESPACE tables_hdfs;

​ 3)向表中插入数据

insert into abc select * from region10;

3.3 基于EC+ODBC的跨集群访问数据

GaussDB(DWS)支持通过 EC(全称Extension Connector)+ODBC统一访问其它大数据组件——将SQL发给其它大数据组件并接收执行结果,实现跨集群访问数据。目前EC+ODBC为用户提供了三种功能: SQL on Oracle、SQL on Spark和SQL on other GaussDB,分别用于连接Oracle数据库、Spark集群和其他GaussDB集群。

EC+ODBC的基本工作原理是:用户首先构建Data Source对象(其中包含目标库的一些连接信息和字符编码方式),然后用户获取该Data Source的使用权限,最后通过标准ODBC API连接目标库,发送SQL语句并获取执行结果。

为了方便使用,EC+ODBC为用户提供了统一的连接函数exec_on_extension(text, text)。其中,第一个参数为Data Source名称,第二个参数为发送的SQL语句,例如:

postgres=# SELECT * FROM exec_on_extension('ds_spark', 'select * from a;') AS (c1 int);

5. GaussDB(DWS) SQL On Anywhere的实现方式优缺点对比

| ELK | HDFS | 1. 支持多DN并发查询 2. 支持和本地多表join查询和写入 3. 支持analyze收集统计信息 4. 节点本地化效率相对比较高 5. 支持增量写,支持update和delete | 1. HDFS表空间方式要求HDFS集群与MPPDB集群有强依赖关系,不易于扩展 2. 格式支持有限,目前只支持ORC格式,并且只支持访问HDFS文件系统 3. 有可能会产生大量小文件 |

点击关注,第一时间了解华为云新鲜技术~