WPF桌面端开发3-数据绑定(MVVM)

数据绑定注意点

  1. 在给组件赋值的时候我们只能在UI线程中处理,同样也只能在UI线程中对DataContext已绑定的对象进行操作。
  2. 文本组件可以绑定int和double类型,不用必须为string类型。

另外提一下

我们在做JSON转换的时候,数字类型的值可能为空的时候,直接转对象必须属性为可空类型。

有时候为了方便处理,我们可以把属性定义为string,这样也是能成功转换的。

数据绑定

定义基类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
using System.ComponentModel;

namespace Z.Common
{
public class ZNotifyModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;

public void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}

定义数据源的类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class ToolbarModel : ZNotifyModel
{
public ObservableCollection<ToolbarMenu> menuList { get; set; }
bool _IsRight = true;
public bool IsRight
{
get { return _IsRight; }
set { _IsRight = value; OnPropertyChanged("IsRight"); }
}
public ToolbarModel()
{
menuList = new ObservableCollection<ToolbarMenu>();
}
}

public class ToolbarMenu : ZNotifyModel
{
string _name;
public string Name
{
get { return _name; }
set { _name = value; OnPropertyChanged("Name"); }
}

public string Pic { get; set; }
}

上面例子中我们可以看到,

如果我们要在数据改变时通知页面改变的属性都要在Set方法中调用OnPropertyChanged

而列表不再用List,而是使用ObservableCollection

ObservableCollection

注意ObservableCollection中调用Add方法会触发列表刷新,但是如果直接更换了对象就不会刷新了。

方式1

如下示例

1
2
3
4
5
6
7
8
9
10
int pageSize = 15;
int totalPage = (int)Math.Ceiling(1.0 * this._pageData.list.Count / pageSize);
if (totalPage == 0)
{
totalPage = 1;
}
this._pageData.totalPage = totalPage;
int startIndex = (this._pageData.currPage - 1) * pageSize;
IEnumerable<JiandaDetailUser> jiandaDetailUsers = this._pageData.list.Skip(startIndex).Take(pageSize);
this._pageData.pageList = new ObservableCollection<JiandaDetailUser>(jiandaDetailUsers);

其中pageList必须调用OnPropertyChanged方法才会刷新

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class JianDaDetailPageData : ZNotifyModel
{
public ObservableCollection<JiandaDetailUser> list { get; set; }

private ObservableCollection<JiandaDetailUser> _pageList;

public ObservableCollection<JiandaDetailUser> pageList
{
get => _pageList;
set
{
_pageList = value;
OnPropertyChanged("pageList");
}
}
}

方式2

如果pageList保持原样那么赋值只能使用Add方法

1
2
3
4
5
this._pageData.pageList.Clear();
foreach (JiandaDetailUser jiandaDetailUser in jiandaDetailUsers)
{
this._pageData.pageList.Add(jiandaDetailUser);
}

对应

1
2
3
4
public class JianDaDetailPageData : ZNotifyModel
{
public ObservableCollection<JiandaDetailUser> pageList { get; set; }
}

设置数据源

代码中也要进行数据源的设置

1
2
3
4
5
6
7
pageData.IsRight = true;
pageData.menuList.Add(new ToolbarMenu()
{
Name = "开始直播",
Pic = "Images/ToolBar/toobar_12_1.png"
});
DataContext = pageData;

上面设置整个页面的数据,当然也可以设置某个组件的数据源

1
this.toolbar_list.DataContext = mydata;

页面中绑定值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<Window.Resources>
<DataTemplate x:Key="ToolbarMenu">
<Button x:Name="toolbar_item" Background="Transparent" BorderThickness="0" Cursor="Hand" Height="60" Click="toolbar_item_Click">
<Button.Content>
<StackPanel Width="Auto" Background="Transparent">
<Image HorizontalAlignment="Center" Width="44" Source="{Binding Pic}"/>
<TextBlock HorizontalAlignment="Center" Text="{Binding Name}" Foreground="#3C525B"/>
</StackPanel>
</Button.Content>
</Button>
</DataTemplate>
</Window.Resources>
<ItemsControl
x:Name="toolbar_list"
ItemsSource="{Binding menuList}"
ItemTemplate="{StaticResource ToolbarMenu}"
ScrollViewer.VerticalScrollBarVisibility="Disabled"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
Grid.Row="1" Background="#f3f3f3" BorderThickness="0">
</ItemsControl>

双向绑定

1
2
<TextBox Grid.Row="0" Text="{Binding Title,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}"/>
<TextBlock Grid.Row="0" Text="{Binding Path=Title}"/>

关键属性 UpdateSourceTrigger=PropertyChanged,Mode=TwoWay

通用值转换

经常我们在绑定数据的时候需要的数据类型和原数据不同,这时候我们就需要对数据进行转换

工具类

C#

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
namespace Z.Converters
{
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Data;
using System.Windows.Media;

public class ZGenericTypeConverter : IValueConverter
{
/// <summary>
/// 正向键值对字典
/// </summary>
private Dictionary<string, string> Alias { get; set; }

/// <summary>
/// 反向键值对字典
/// </summary>
private Dictionary<string, string> BackAlias { get; set; }

private string _aliasStrTemp = "";

/// <summary>
/// 解析转换规则
/// </summary>
/// <param name="aliasStr">
/// 规则字符串
/// </param>
private void ParseAliasByStr(string aliasStr)
{
if (_aliasStrTemp == aliasStr)
{
return;
}

_aliasStrTemp = aliasStr;
Alias = new Dictionary<string, string>();
BackAlias = new Dictionary<string, string>();

string content = aliasStr;

Alias = new Dictionary<string, string>();
string[] arr1 = content.Split('|');
foreach (string item in arr1)
{
string[] arr2 = item.Split(':');
string key = arr2[0];
if (key == "true")
{
key = "True";
}
else if (key == "false")
{
key = "False";
}
string value = arr2[1];
if (key != "other")
{
BackAlias.Add(value, key);
}

Alias.Add(key, value);
}
}

private object ConvertCommon(object value, Type targetType, object parameter, bool isBack)
{
if (value == null || string.IsNullOrEmpty(parameter.ToString()))
{
return null;
}

object ret = value;//如果没有匹配返回传入的值

ParseAliasByStr(parameter.ToString());
Dictionary<string, string> alias;
if (isBack)
{
alias = BackAlias;
}
else
{
alias = Alias;
}

//绑定的值
string bindingValue = value.ToString();

if (alias.ContainsKey(bindingValue))
{
ret = StringToTargetType(alias[bindingValue], targetType);
}
else if (alias.ContainsKey("other"))
{
ret = StringToTargetType(alias["other"], targetType);
}
else if (alias.ContainsKey("else"))
{
ret = StringToTargetType(alias["else"], targetType);
}

return ret;
}

/// <summary>
/// 字符串转换成目标类型,如需添加一个目标类型只需在该方法中添加一个类型判断之后转换
/// </summary>
/// <param name="strValue">
/// </param>
/// <param name="targetType">
/// </param>
/// <returns>
/// </returns>
private object StringToTargetType(string strValue, Type targetType)
{
object ret = null;
//目标类型 string
if (targetType == typeof(string) || targetType == typeof(char))
{
ret = strValue;
}
//目标类型 char
if (targetType == typeof(char))
{
if (strValue.Length == 1)
{
ret = strValue;
}
}
//目标类型 int
if (targetType == typeof(int))
{
if (int.TryParse(strValue, out int temp))
{
ret = temp;
}
else
{
ret = 0;
}
}
//目标类型 double
if (targetType == typeof(double))
{
if (double.TryParse(strValue, out double temp))
{
ret = temp;
}
else
{
ret = 0;
}
}
//目标类型 float
if (targetType == typeof(float))
{
if (float.TryParse(strValue, out float temp))
{
ret = temp;
}
else
{
ret = 0;
}
}
//目标类型 decimal
if (targetType == typeof(decimal))
{
if (decimal.TryParse(strValue, out decimal temp))
{
ret = temp;
}
else
{
ret = 0;
}
}
//目标类型 bool? bool
if (targetType == typeof(bool?) || targetType == typeof(bool))
{
if (bool.TryParse(strValue, out bool temp))
{
ret = temp;
}
else
{
ret = false;
}
}

//目标类型 Visibility
if (targetType == typeof(Visibility))
{
switch (strValue.ToLower())
{
case "collapsed":
ret = Visibility.Collapsed;
break;

case "hidden":
ret = Visibility.Hidden;
break;

case "visible":
ret = Visibility.Visible;
break;

default:
ret = Visibility.Visible;
break;
}
}
if (targetType == typeof(Brush))
{
// 通过RGB字符串创建红色
Color myColor = (Color)ColorConverter.ConvertFromString(
strValue
)!;

// 创建SolidColorBrush对象
SolidColorBrush myBrush = new SolidColorBrush(
myColor
);
ret = myBrush;
}
return ret;
}

public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return ConvertCommon(value, targetType, parameter, false);
}

public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return ConvertCommon(value, targetType, parameter, true);
}
}
}

使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

<Window
x:Class="card_scanner.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:converter="clr-namespace:Z.Converter"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:card_scanner"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="MainWindow"
Width="664"
Height="502"
AllowsTransparency="False"
Background="White"
Closed="Window_Closed"
MouseLeftButtonDown="Window_MouseLeftButtonDown_1"
ResizeMode="NoResize"
WindowStartupLocation="CenterScreen"
WindowStyle="None"
mc:Ignorable="d">
<Window.Resources>
<converter:ZGenericTypeConverter x:Key="ZGenericTypeConverter" />
</Window.Resources>
<Border
Width="80"
Height="120"
BorderBrush="#CCCCCC"
BorderThickness="1"
CornerRadius="2,2,2,2"
Visibility="{Binding Path=ISChecked, Converter={StaticResource ZGenericTypeConverter}, ConverterParameter='true:Visible|false:Hidden'}" />

</Window>

颜色

1
Background="{Binding Path=KYFile, Converter={StaticResource ZGenericTypeConverter}, ConverterParameter='0:#666666|1:#019266|2:#FF001E'}"

前景色

1
Foreground="{Binding Path=KYFile, Converter={StaticResource ZGenericTypeConverter}, ConverterParameter='0:#666666|1:#019266|2:#FF001E'}"

边框

1
2
3
4
5
<Border
BorderBrush="{Binding Path=Rank, Converter={StaticResource ZGenericTypeConverter}, ConverterParameter='1:#FFA639|2:#1584FC|3:#FF7444|other:#EEEEEE'}"
BorderThickness="1">

</Border>

文字

1
Text="{Binding Path=KYFile, Converter={StaticResource ZGenericTypeConverter}, ConverterParameter='0:未检测|1:正常|2:异常'}"

绑定是否显示

1
Visibility="{Binding Path=selectIndex, Converter={StaticResource ZGenericTypeConverter}, ConverterParameter='1:Visible|other:Hidden'}"

不同状态切换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<Image
Width="17"
Height="17"
Margin="16,0,16,0"
VerticalAlignment="Center"
Source="/Images/SheZhiMore/shezhi10@2x.png"
Visibility="{Binding Path=selectIndex, Converter={StaticResource ZGenericTypeConverter}, ConverterParameter='1:collapsed|other:visible'}" />
<Image
Width="17"
Height="17"
Margin="16,0,16,0"
VerticalAlignment="Center"
Source="/Images/SheZhiMore/shezhi11@2x.png"
Visibility="{Binding Path=selectIndex, Converter={StaticResource ZGenericTypeConverter}, ConverterParameter='1:visible|other:collapsed'}" />

自定义代码片段

上面属性写的时候比较麻烦,建议使用自定义代码片段。

在任意地方创建一个文件夹,最好是你不去经常移动的地方,文件夹是用来存放你自定义的代码块的文件夹,

我就创建了一个名称:csharp_snippet 的文件夹

新建代码块文件zprop.snippet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<?xml version="1.0" encoding="utf-8"?>
<CodeSnippets xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
<CodeSnippet Format="1.0.0">
<Header>
<Title>zprop</Title>
<Shortcut>zprop</Shortcut>
<Description>自动实现的属性的代码片段</Description>
<Author>剑行者</Author>
<SnippetTypes>
<SnippetType>Expansion</SnippetType>
</SnippetTypes>
</Header>
<Snippet>
<Declarations>
<Literal>
<ID>type</ID>
<ToolTip>属性类型</ToolTip>
<Default>string</Default>
</Literal>
<Literal>
<ID>property</ID>
<ToolTip>属性名</ToolTip>
<Default>Name</Default>
</Literal>
</Declarations>
<Code Language="csharp">
<![CDATA[
$type$ _$property$;
public $type$ $property$
{
get { return _$property$; }
set { _$property$ = value; OnPropertyChanged("$property$"); }
}
$end$]]>
</Code>
</Snippet>
</CodeSnippet>
</CodeSnippets>

保存啦,然后依然是去 工具–>代码段管理器 –>选择Visual C#语言 –>选择下方的添加 –>浏览到你自定义的那个放代码块的文件夹就OK啦。

重启开发工具。

此时要我在项目中打出zprop按两次Tab 那我的数据访问层的代码就全部出来啦,当然还要添加一些引用就可以啦