用MVVM模式开发中遇到的零散问题总结(1)
关看这个标题略显业余,其中的任何一个问题都是困扰我几个小时才找到答案的,以供以后温故而知新,希望也能帮助到你
本节碰到的问题如下:
2.将一个字符串作为Xaml的resources供ViewModel调用
4.通过可视化树来修改动态创建的UserControl的模版(Template)内容
string url = "view/V" + Convert.ToString(i + 1) + "/V" + Convert.ToString(i + 1)+"1.xaml";//生成地址
FileStream fs = new FileStream(url, FileMode.Open, FileAccess.Read);//动态加载XAML
UserControl uc = XamlReader.Load(fs) as UserControl;
myContentControl.Content = uc;
其实就是先动态读入XAML文件流,再反序列化,生成的实力赋到容器中去,这里我的XAML是以usercontrol为根节点的。
注意:在写ViewModel的命名空间时一定要加上assembly,如:
xmlns:my="clr-namespace:CopSurface;assembly=CopSurface"
2.将一个字符串作为Xaml的resources供ViewModel调用
命名空间:xmlns:sys="clr-namespace:System;assembly=mscorlib"
然后 <sys:String x:Key="title">啦啦啦</sys:String> 添加进资源
后台: stateButton.Content = uc.TryFindResource("title") as string;
看图可见,基本所有类型都有了
定义:
public Dictionary<string, object> data//数据字典
{
get;
set;
}
赋值并更新界面:
model.data["inputText"] += str;
model.OnPropertyChanged("data");
XAML:
<TextBox Text="{Binding data[inputText],Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
其中DataContent、OnPropertyChanged这些你们懂的,不懂的去看看MVVM基础。
这个问题困扰了我一个星期,就这么简单?是这么简单,只是我的问题出在别处,我尝试了CollectionObvious和MR.WPF的dictionaryObvious类都没有效果,只能实现单项绑定,dictionary的更新总是通知不到界面...
原来问题是由于我是新建了一个线程创建来窗口,必须要获取该窗口的Dispatcher,然后用它来执行OnPropertyChanged(),就可以更新了,真的是基础不牢靠的后果啊...
win.win.Dispatcher.Invoke(new Action(() =>
{
if (!model.data.ContainsKey(nameStr))//如果字典中不存在则创建
{
model.data.Add(nameStr, "");
model.data[nameStr] += str;
model.OnPropertyChanged("data");
}
else
{
int index=focusTextBox.CaretIndex;//获取光标位置
focusTextBox.Text = focusTextBox.Text.Insert(index,str);//在光标位置插入字符串
focusTextBox.SelectionStart = index+1;//光标位置后移1位
}
}));
细心的博友已经发现为什么我要绑定到dictionary而不直接绑定到属性呢?优势不言而喻,当我的View为一张银行用户开户单时那个数据啊~~是不可能设置这么多属性来一一对应的,这样当View层的Command命令执行过来时先判断dictionary中是否已经存在该项,没有就添加,这样通用性就很强了。
4.通过可视化树来修改动态创建的UserControl的模版(Template)内容
通常我们需要修改Template模版内的具体属性时通过VisualTreeHelper这个类来实现,一般情况下(为了突出重点,我把代码精简了点):
<ListBox x:Name="listBox" Style="{DynamicResource ListBoxStyle1}"/>
然后模版:
<Style x:Key="ListBoxStyle1" TargetType="{x:Type ListBox}">
。。。。。。
<ControlTemplate TargetType="{x:Type ListBox}">
<Border x:Name="Bd" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Padding="1" SnapsToDevicePixels="true">
<ScrollViewer x:Name="myScrollViewer" Focusable="false" Padding="{TemplateBinding Padding}" Template="{DynamicResource ScrollViewerControlTemplate1}">
<ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
</ScrollViewer>
</Border>
。。。。。。。。。。。。。
</ControlTemplate>
</Style>
后台:
Border myBorder = VisualTreeHelper.GetChild(listBox, 0) as Border;
ScrollViewer myScrollViewer = myBorder.FindName("myScrollViewer") as ScrollViewer;
return myScrollViewer;
这样就获取到ListBox中的myScrollViewer控件了,从而就可以控制滚动条的滑动。
而..如果我ListBox是后台动态生成的呢?比如,我需要动态生成一堆相同样子的控件,模版我定义在资源里
模版资源
<ControlTemplate x:Key="menuStyle">
<Grid Width="80" Height="80">
<Grid.Background>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#FFA0A5E7" Offset="1"/>
<GradientStop Color="#FFEDEDEF"/>
</LinearGradientBrush>
</Grid.Background>
<Label x:Name="title" Margin="0,31.5,0,19"/>
<Label Name="state" Height="20.837" VerticalAlignment="Top" HorizontalAlignment="Right" Width="20.457"/>
</Grid>
</ControlTemplate>
后台生成:
UserControl menu = new UserControl();
menu.Template = TryFindResource("menuStyle") as ControlTemplate;//把模版资源付过去
buttonList.Items.Add(menu);
Grid myBorder = VisualTreeHelper.GetChild(sender as FrameworkElement, 0) as Grid;
Label myLabel = (myBorder.FindName("title") as Label);
myLabel.Content = "OK";
这个时候在执行VisualTreeHelper.GetChild()方法时就会报错“超出索引...”,为什么呢?很简单....因为可视树还没有生成,当然获取不到子元素咯....和我一样基础不牢靠的赶紧补基础...
正确答案为:
menu.Template = TryFindResource("menuStyle") as ControlTemplate;//把模版资源付过去
menu.Loaded+=new RoutedEventHandler(menu_Loaded);//当加载完毕后才能查找可视化树
buttonList.Items.Add(menu);
private void menu_Loaded(object sender, RoutedEventArgs e)
{
Grid myBorder = VisualTreeHelper.GetChild(sender as FrameworkElement, 0) as Grid;
Label myLabel = (myBorder.FindName("title") as Label);
myLabel.Content = "OK";
}
如此简单...确纠结了我一下午....基础啊..
很多人看到这个标题的时候首先就联想到如下代码:
<Grid Margin="0" Width="Auto" Background="#00000000" >
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseLeftButtonUp">
<i:InvokeCommandAction Command="{Binding changeContent}" CommandParameter="{Binding XPath=content}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Grid>
问题的关键不在于此,当在Blend中直接拖动行为InvokeCommandAction到控件上就会直接加上上述代码,并且添加命名空间
但是当我们在VS2010中直接写代码来实现的时候就不能用这个命名空间,不然报错找不到Interaction.Triggers要用下面这个命名空间
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
并添加程序集引用:System.Windows.Interactivity.dll
(上面的XAML有一个小技巧就是当Grid无背景色时是响应不了鼠标事件的,但是给它添加一个透明度为0的颜色为背景色就可以触发了,但是当我把它用在listBox的itemTemplate里面的时候,又触发不了了,求解释)
所谓不认识的文件就是文件不是在项目中添加的,而是直接Copy到exe文件目录里面。
<Grid.Background>
<ImageBrush ImageSource="pack://SiteOfOrigin:,,,/View/menuImg/bg.jpg"/>
</Grid.Background>
当然了,.exe实在Bin目录下。和网页的区别就是并不是相对于XAML的目录。
这样使用在Blend里会报错
不用管它,直接编译就行。
更新:在Blend里把"pack://SiteOfOrigin:,,,/View/menuImg/bg.jpg"改为"pack://siteoforigin:,,,/View/menuImg/bg.jpg"就是把SiteOfOrigin的所有大写都改为小写,就可以完美显示了,而且还不会报错,有点-_-!。