View is not binding correctly to ViewModel - wpf

I cannot get the View to bind correctly to the ViewModel. When it displays, it only shows the string version of the ViewModel.
I have seen: Setting Window.Content to ViewModel - simple data template not working. But the link is no longer available.
I'm trying to use https://msdn.microsoft.com/en-us/magazine/dd419663.aspx, as a template.
MainWindow.xaml
<Window x:Class="DemoApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:DemoApp.ViewModel"
xmlns:vw="clr-namespace:DemoApp.View">
<Window.Resources>
<DataTemplate DataType="{x:Type vm:TestViewModel}">
<vw:TestView/>
</DataTemplate>
<DataTemplate x:Key="ClosableTabItemTemplate">
<DockPanel Width="120">
<Button
Command="{Binding Path=CloseCommand}"
Content="X"
Cursor="Hand"
DockPanel.Dock="Right"
VerticalContentAlignment="Bottom"
Width="16" Height="16"/>
<ContentPresenter
Content="{Binding Path=DisplayName}"
VerticalAlignment="Center"/>
</DockPanel>
</DataTemplate>
<DataTemplate x:Key="WorkspacesTemplate">
<TabControl
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding}"
ItemTemplate="{StaticResource ClosableTabItemTemplate}"
Margin="4" />
</DataTemplate>
</Window.Resources>
<DockPanel>
<Border
Grid.Column="2"
Style="{StaticResource MainBorderStyle}">
<HeaderedContentControl
Content="{Binding Path=Workspaces}"
ContentTemplate="{StaticResource WorkspacesTemplate}"
Header="Workspaces"
Style="{StaticResource MainHCCStyle}" />
</Border>
</DockPanel>
</Window>
MainWindowViewModel.cs
// ommitted for clarity. This is directing to the view model correctly. It's the binding between View and ViewModel that is not
TestView.xaml
public class TestViewModel : WorkspaceViewModel, INotifyPropertyChanged,
{
public Model.Test _test;
public string DisplayName {get; set;}
public class TestViewModel(Model.Test t)
{
DisplayName = "Test Display Name";
_model = t;
}
// INofifyPropertyChanged Members removed for clarity
}
Test.cs
public class Test
{
public string FirstName {get; set;}
public string LastName {get; set;}
public static DisplayTest()
{
return new Test();
}
}
Displays:
DemoApp.ViewModel.TestViewModel;
However, when I go to the MainWindow.xaml and actually type in into a DockPanel, it will display correctly...
Thank you!!
UPDATE:
MainWindowViewModel.cs Properties
public ReadOnlyCollection<CommandViewModel> Commands
{
get
{
if (_commands == null)
{
List<CommandViewModel> cmds = this.CreateCommands();
_commands = new ReadOnlyCollection<CommandViewModel>(cmds);
}
return _commands;
}
}
public ObservableCollection<WorkspaceViewModel> Workspaces
{
get
{
if (_workspaces == null)
{
_workspaces = new ObservableCollection<WorkspaceViewModel>();
_workspaces.CollectionChanged += this.OnWorkspacesChanged;
}
return _workspaces;
}
}

In the View there was a Data Context Declared. This was confusing the binding it looks like. Once the Data Context in the View was removed and the MainWindowResourses kept the data context, the view is displayed as it should.

Related

How do I initialize viewmodel's datacontext when the application starts?

I started to use MVVMLight framework recently.
When I use ContentPresenter to swtich between ViewModel, it seems like it initialize the datacontext when it first displays.
However, I want it to initialize it's datacontext so it can keep track of any change from the initial loading of the application, or at least share the data with other viewmodel(I assume maybe I can use dataservice to keep track of all the data, but I could not find a right example to use it with contentpresenter & MVVMLight).
Below is the sample code I made. When I click Review button, "usercontrolview" will display "Picture Saved", however "contentpresenterview" will display "No Picture".
Sample Image
MainView.xaml
<Grid>
<Button x:Name="CaptureButton" Content="Capture" HorizontalAlignment="Left" Margin="34,10,0,0" VerticalAlignment="Top" Width="75"
Command="{Binding CaptureCommand}"/>
<Button x:Name="ReviewButton" Content="Review" HorizontalAlignment="Left" Margin="146,10,0,0" VerticalAlignment="Top" Width="75"
Command="{Binding ShowReviewCommand}"/>
<TextBlock FontSize="36"
FontWeight="Bold"
Foreground="Purple"
Text="{Binding CaptureStatus}"
VerticalAlignment="Center"
HorizontalAlignment="Center"
TextWrapping="Wrap" />
<Grid x:Name="usercontrolview" Visibility="{Binding ReviewModeOn, Converter={StaticResource BooleanToVisibilityConverter}}" Margin="0,50,150,0">
<view:ReviewView/>
</Grid>
<ContentPresenter x:Name="contentpresenterview" Content="{Binding CurrentContent}" Margin="150,50,0,0"/>
</Grid>
MainViewModel.cs (Partial)
public MainViewModel()
{
CaptureStatus = "No Picture";
CaptureCommand = new RelayCommand(Capture);
ShowReviewCommand = new RelayCommand(ShowReview);
ReviewModeOn = false;
}
public RelayCommand CaptureCommand { get; private set; }
private void Capture()
{
CaptureStatus = "Pictures Saved";
Messenger.Default.Send<NotificationMessage>(new NotificationMessage("Pictures Saved"), "Captured");
}
public RelayCommand ShowReviewCommand { get; private set; }
private void ShowReview()
{
ReviewModeOn = !ReviewModeOn;
CurrentContent = ContentViewModel;
}
And my template for ReviewViewModel & ReviewContentPresenterViewModel
public ***ViewModel()
{
Messenger.Default.Register<NotificationMessage>(this, "Captured", Captured);
CaptureStatus = "No Picture";
}
private void Captured(NotificationMessage notificationMessage)
{
CaptureStatus = notificationMessage.Notification;
}
private string _captureStatus;
public string CaptureStatus
{
get { return _captureStatus; }
set { Set(ref _captureStatus, value); }
}
======================= Update =======================
My template for ReviewView & ReviewContentPresenterView.
It takes DataContext by locator.
<UserControl x:Class="***View"
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:ignore="http://www.galasoft.ch/ignore"
mc:Ignorable="d ignore"
DataContext="{Binding ***ViewModel, Source={StaticResource Locator}}">
<Grid Background="Gray">
<TextBlock FontSize="36"
FontWeight="Bold"
Foreground="Purple"
Text="{Binding CaptureStatus}"
VerticalAlignment="Center"
HorizontalAlignment="Center"
TextWrapping="Wrap" />
</Grid>
</UserControl>
It seems your DataContext in the root Grid and all it's children is the MainViewModel and you want other DataContext for ContentPresenter. Create the other ViewModel as a resource e.g.
<Grid>
<Grid.Resources>
<local:TheViewModelIWant x:Key=ContentViewModel/>
</Grid.Resources>
...
...
<ContentPresenter DataContext={StaticResource ContentViewModel} ...
(By the way, if you are already creating an instance of TheViewModelIWant somewhere e.g. in other ViewModels or view code behind then you can stop using that instance and access this instance in C# code as Resources["ContentViewModel"])

UWP XAML How do I wrap text in a bound ListView

How do I wrap or otherwise display long strings in my listview control. I have been unsuccessful in wrapping, or otherwise displaying long text in my bound ListView control. My xaml page is basically a BOUND FlipView with an ItemTemplate that contains two bound textBlocks and a bound ListView. I can get the TextBlocks to wrap but not the listviewitems. It would seem like such a simple thing yet it eludes me.
Here is a portion of my xaml:
<Page.Resources>
<DataTemplate x:DataType="data:MydataObject" x:Key="MydataObjectTemplate">
<StackPanel HorizontalAlignment="Stretch" Height="596" Width="982">
<TextBlock Name="txtDataObjectId" Text="{Binding dataObject.Id}" Visibility="Collapsed" TextWrapping="WrapWholeWords"/>
<TextBlock FontSize="24" Text="{x:Bind dataObject}" HorizontalAlignment="Center" TextWrapping="WrapWholeWords"/>
<ListView ItemsSource ="{x:Bind theObjectDetails, Mode=OneWay }"
HorizontalAlignment="Stretch"
BorderBrush="Black"
BorderThickness="1"/>
</StackPanel>
</DataTemplate>
</Page.Resources>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<StackPanel HorizontalAlignment="Stretch">
<ComboBox x:Name="cboCategory" Header="Category" SelectionChanged="cboCategory_SelectionChanged" />
<FlipView x:Name="FlipView1"
ItemsSource="{x:Bind MydataObjects, Mode=OneWay }"
ItemTemplate="{StaticResource MydataObjectTemplate}"
BorderBrush="Black"
BorderThickness="1"/>
</StackPanel>
</Grid>
//c#
public class mydataObject
{
public int Id { get; set; }
public dataObject theObject { get; set; }
public List<dataObjectDetails> theObjectDetails { get; set; }
public override string ToString()
{
return this.theObject.Subject;
}
}
public class dataObjectDetails
{
public int Id { get; set; }
public int dodId{ get; set; }
public string bodyText { get; set; }
public override string ToString()
{
return bodyText ;
}
}
Give the ListView an ItemTemplate, which puts the content in a TextBlock that wraps the text:
<ListView
ItemsSource="{x:Bind theObjectDetails, Mode=OneWay}"
HorizontalAlignment="Stretch"
>
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<TextBlock
Text="{Binding bodyText}"
TextWrapping="WrapWholeWords"
/>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>

DataTemplate for ViewModel slow to display

I have a simple MainWindowView
<Window ...>
<Window.Resources>
<DataTemplate DataType="{x:Type local:ClassListForTeacherViewModel}">
<local:ClassListForTeacher />
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="109*"/>
<ColumnDefinition Width="408*"/>
</Grid.ColumnDefinitions>
<local:NavigationControl Grid.Column="0"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
Width="Auto"/>
<ContentControl Content="{Binding MainView}"
Grid.Column="1"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
Width="Auto"/>
</Grid>
<Window>
and a MainWindowViewModel
public class MainWindowViewModel : ViewModelBase
{
public MainWindowViewModel(string name)
: this(name, null)
{
}
public MainWindowViewModel(string name, ViewModelBase mainView)
{
Name = name;
MainView = mainView;
NavigateToTeacherClassesCommand = new DelegateCommand(o => NavigateToTeacherClasses());
}
//
// Properties
//
public string Name { get; private set; }
private ViewModelBase _MainView;
public ViewModelBase MainView
{
get
{
return _MainView;
}
private set
{
_MainView = value;
RaisePropertyChangedEvent("MainView");
}
}
public ICommand NavigateToTeacherClassesCommand { get; private set; }
//
// Functions
//
private void NavigateToTeacherClasses()
{
MainView = new ClassListForTeacherViewModel();
}
}
The ClassListForTeacher view only contains a ListView control with no logic (yet). When I click the Label in the NavigationControl in the MainWindowView, the ClassListForTeacher is set on the MainView property of MainWindowView and is displayed via the DataTemplate. This is barebones program; no loading data yet. My problem is the display takes too long, maybe about a full 3 seconds. It is my first time trying out MVVM, I see this is a common technique (using DataTemplate), is this normal?
I also noticed that the ViewModel for ClassListForTeacher is called twice but I have no other code calling it. One from the NavigateToTeacherClasses function, the other I don't know. When WPF actually resolves the DataTemplate (which is a UserControl), is the constructor called again? If so, How do I prevent this?
EDIT:
XAML for NavigationControl
<UserControl...
<StackPanel>
<Expander Header="Grading" IsExpanded="True">
<StackPanel Background="#FFE5E5E5">
<Label Content="My Classes">
<Label.InputBindings>
<MouseBinding Command="{Binding NavigateToTeacherClassesCommand}" MouseAction="LeftClick" />
</Label.InputBindings>
</Label>
</StackPanel>
</Expander>
</StackPanel>
</UserControl>
XAML for ClassListForTeacher:
<UserControl...
<Grid x:Name="LayoutRoot">
<ListView>
<ListView.View>
<GridView>
<GridViewColumn/>
</GridView>
</ListView.View>
</ListView>
</Grid>
</UserControl>

Silverlight PropertyGrid-like binding

I know that there are 3rd party silverlight property grids (in fact my company owns one) so please dont suggest 3rd party controls : I am trying to learn more about binding in xaml with this question.
I am writing a Silverlight front end as a sign facade to launch SSRS, and php based reports.
I have created a Report class with information about the report, and it has a parameters collection containing the information about parameters that need to be filled in to run the report.
My plan is to create a silverlight property grid that is bound to the Parameters collection of the Report.
Here's a simpler version of the classes:
public class Report
{
public int ReportId { get; set; }
public string ReportName { get; set; }
public string Description { get; set; }
private List<ReportParameter> _Parameters = new List<ReportParameter>();
public List<ReportParameter> Parameters
{
get { return _Parameters; }
set { _Parameters = value; }
}
}
public class ReportParameter
{
public int ReportId { get; set; }
public string ParameterName { get; set; }
public string DataTemplateName { get; set; }
public bool IsRequired { get; set; }
}
I was hoping to use the DataTemplateName property of ReportParameter to bind to data templates: for example if I have a parameter that is a date, I want to be able to set DataTemplateName="MyDatePicker" and then DataTemplate={StaticResource {Binding DataTemplateName}} and have that row use a DataTemplate defined in the Resources for editing the parameter value.
Here's some XAML I am using to try to get it to work:
<UserControl x:Class="ReportLauncherWorkbench.MainPage"
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:ReportLauncherWorkbench"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<UserControl.Resources>
<local:Report x:Key="MyData"
ReportName="Rick Report"
Description="Great report, try it!"
ReportId="0"
>
<local:Report.Parameters>
<local:XReportParameter DataTemplateName="DatePickerTemplate"
ParameterName="StartDate"
IsRequired="True"
Tooltip="Please enter the start date"
/>
<local:XReportParameter DataTemplateName="CheckBoxTemplate"
ParameterName="AmIHot"
IsRequired="True"
Tooltip="Please check here if you are hot"
/>
</local:Report.Parameters>
</local:Report>
<DataTemplate x:Key="DatePickerTemplate">
<StackPanel Orientation="Horizontal">
<TextBox />
<Button Content="..."/>
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="CheckBoxTemplate">
<StackPanel Orientation="Horizontal">
<CheckBox/>
</StackPanel>
</DataTemplate>
</UserControl.Resources>
<Grid x:Name="LayoutRoot" Background="White" DataContext="{StaticResource MyData}">
<Grid x:Name="Test1" Background="White">
<StackPanel>
<TextBlock Text="{Binding ReportName}"/>
<TextBlock Text="{Binding Description}"/>
<ListBox ItemsSource="{Binding Parameters}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50"></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding ParameterName}" Grid.Column="1" Margin="5"/>
<ListBox ItemsSource="{Binding}" Grid.Column="2" Width="100" ItemTemplate="{StaticResource {Binding DataTemplateName}}">
<!-- I want to somehow bind which DataTemplate is rendered-->
</ListBox>
</Grid>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</Grid>
</Grid>
</UserControl>
Thanks!
I figured it out - with the help of a great FAQ on the silverlight forum:
http://forums.silverlight.net/p/95440/218611.aspx
Silverlight does not have the DataTemplateSelector class that WPF does, which would have solved the problem.
In section titled
7.1 What data binding features of WPF are not yet supported in Silverlight? Is there a workaround?
There is a simple workaround that for DataTemplateSelector functionality.
So here's how I fixed it in my code sample:
Replace the Listbox with the following:
<ListBox ItemsSource="{Binding Parameters}">
<ListBox.ItemTemplate>
<DataTemplate>
<ContentControl Content="{Binding}" Loaded="ContentControl_Loaded"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
And then fill in the ContentControl_Loaded event in the code behind with:
private void ContentControl_Loaded(object sender, RoutedEventArgs e)
{
ContentControl cc = (ContentControl) sender;
XReportParameter p = (XReportParameter)cc.DataContext;
cc.ContentTemplate = (DataTemplate)this.Resources[p.DataTemplateName];
}
Works great!

Two views of the same ViewModel within a user control

I have a re-usable usercontrol with a viewmodel behind it. I'm trying to switch between different views of the same data. Currently trying to use a Mode property on the VM to accomplish this.
I've created a DataTemplateSelector like so:
<UserControl x:Class="MyUserControl">
<UserControl.Resources>
<DataTemplate x:Key="ColumnTemplate">
<StackPanel>
<Label Text="{Binding Name}"></Label>
<Label Text="{Binding Address}"></Label>
<Label Text="{Binding Occupation}"></Label>
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="AvatarTemplate">
<StackPanel>
<Image Source="{Binding ProfilePicture}"></Image>
<Label Text="{Binding Name}"></Label>
</StackPanel>
</DataTemplate>
<local:DisplayTemplateSelector ColumnTemplate="{StaticResource ColumnTemplate}" AvatarTemplate="{StaticResource AvatarTemplate}" x:Key="displayTemplateSelector" />
</UserControl.Resources>
<Grid>
<ContentControl Name="cpDisplay" Content="{Binding}" ContentTemplateSelector="{StaticResource displayTemplateSelector}" />
</Grid>
</UserControl>
With the class:
class DisplayTemplateSelector : DataTemplateSelector
{
public DataTemplate ColumnTemplate {get;set;}
public DataTemplate AvatarTemplate {get;set;}
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
MainViewModel vm = (MainViewModel)item;
switch (vm.Mode)
{
case MainViewModel.DisplayMode.Column:
return ColumnTemplate;
case MainViewModel.DisplayMode.Avatar:
return AvatarTemplate;
default:
return AvatarTemplate;
}
}
}
This usercontrol sits in MyWindow:
<Grid>
<controls:MyUserControl x:Name="MyUserControl" DataContext="{Binding}" Margin="0"/>
</Grid>
Which is instantiated with my viewmodel:
MyWindow w = new MyWindow(_vm);
w.Show();
The problem I have is that item is null during MainViewModel vm = (MainViewModel)item. It's like I'm trying to set the datatemplate based on data, before the data is bound?
Is there anyway to choose the desired datatemplate not based on the dataobject - but as a property or similar on the usercontrol?
There are many ways, but here are a couple:
<!-- assumes you have a data template selector implementation available as resource MyContentSelector -->
<ContentControl Content="{StaticResource MainViewModel}" ContentTemplateSelector="{StaticResource MyContentSelector}"/>
or:
<!-- assumes you have appropriate boolean properties on your VM -->
<Grid>
<ContentControl Content="{StaticResource MainViewModel}" ContentTemplate="{StaticResource column}" Visibility="{Binding IsColumnVisible, Converter={StaticResource BooleanToVisibilityConverter}}"/>
<ContentControl Content="{StaticResource MainViewModel}" ContentTemplate="{StaticResource avatar}" Visibility="{Binding IsAvatarVisible, Converter={StaticResource BooleanToVisibilityConverter}}"/>
</Grid>
See DataTemplateSelector Class
DataTemplateSelector
Ok, finally got this to work how I needed by using a property on the usercontrol and some code behind:
public enum DisplayMode
{
Column,
Avatar
}
public DisplayMode Mode { get; set; }
public MyUserControl()
{
InitializeComponent();
Mode = DisplayMode.Avatar;
}
private void MyUserControl_Loaded(object sender, RoutedEventArgs e)
{
switch (Mode)
{
case DisplayMode.Column:
cpDisplay.ContentTemplate = (DataTemplate)this.Resources["ColumnTemplate"];
cpDisplay.ApplyTemplate();
break;
case DisplayMode.Avatar:
cpDisplay.ContentTemplate = (DataTemplate)this.Resources["AvatarTemplate"];
cpDisplay.ApplyTemplate();
break;
}
}
I removed the DataTemplateSelector code and simply defined the datatemplates and used:
<ContentPresenter Name="cpDisplay" Content="{Binding}" />

Resources