MVVM: Binding a ViewModel which takes constructor args to a UserControl - wpf

My WPF app has a MainWindow containing a usercontrol called TvshowGridView.
MainWindow:
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:NevermissClient"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
xmlns:views="clr-namespace:NevermissClient.Views"
x:Class="NevermissClient.MainWindow"
x:Name="Window">
<Grid x:Name="LayoutRoot">
<views:TvshowGridView x:Name="TheTvshowGridView" Margin="8,8,8,58.96" Grid.Row="1"/>
</Grid>
</Window>
TvshowGridView:
<UserControl
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:viewModels="clr-namespace:NevermissClient.ViewModels"
xmlns:telerik="http://schemas.telerik.com/2008/xaml/presentation"
mc:Ignorable="d"
x:Class="NevermissClient.Views.TvshowGridView"
d:DesignWidth="640" d:DesignHeight="480">
<Grid x:Name="LayoutRoot">
<telerik:RadGridView x:Name="TvshowGrid" d:LayoutOverrides="Width, Height" AutoGenerateColumns="False" ItemsSource="{Binding AllEpisodes}" IsReadOnly="False">
<telerik:RadGridView.Columns>
<telerik:GridViewDataColumn DataMemberBinding="{Binding TvshowName, Mode=TwoWay}" Header="Tvshow Name" IsReadOnly="False"/>
<telerik:GridViewDataColumn DataMemberBinding="{Binding Name, Mode=TwoWay}" Header="Episode Name"/>
<telerik:GridViewDataColumn DataMemberBinding="{Binding Airdate, Mode=TwoWay}" Header="Airdate"/>
</telerik:RadGridView.Columns>
</telerik:RadGridView>
</Grid>
</UserControl>
The view model, TvshowGridViewModel, that I wish to bind to the TvshowGridView has a constructor that takes arguments.
public class TvshowGridViewModel : BaseViewModel
{
private EpisodeRepository _episodeRepository;
private TvshowRepository _tvshowRepository;
public ObservableCollection<EpisodeViewModel> AllEpisodes { get; private set; }
public TvshowGridViewModel(EpisodeRepository episodeRepository, TvshowRepository tvshowRepository)
{
_episodeRepository = episodeRepository;
_tvshowRepository = tvshowRepository;
CreateAllEpisodes();
}
...
}
These arguments are defined in MainWindowViewModel, the view model connected to the MainWindow. - So this seems like the logical place to create the TvshowGridViewModel.
public class MainWindowViewModel : BaseViewModel
{
readonly TvshowGridViewModel _tvshowGridViewModel;
readonly EpisodeRepository _episodeRepository;
readonly TvshowRepository _tvshowRepository;
public MainWindowViewModel()
{
_episodeRepository = new EpisodeRepository("c:\data.xml");
_tvshowRepository = new TvshowRepository("c:\data.xml");
_tvshowGridViewModel = new TvshowGridViewModel(_episodeRepository, _tvshowRepository);
}
public TvshowGridViewModel TvshowGridViewModel { get; }
...
}
How can I bind the instantiated TvshowGridViewModel to the TvshowGridView? (Avoiding codebehind)
Thanks!

Assuming that your MainWindows Datacontext is an instance of MainWindowViewModel, you can bind the usercontrol to TvshowGridViewModel like this:
<Window>
...
<Grid x:Name="LayoutRoot">
<views:TvshowGridView DataContext={Binding TvshowGridViewModel} x:Name="TheTvshowGridView" Margin="8,8,8,58.96" Grid.Row="1"/>
</Grid>
You also should change the TvshowGridViewModel property code like shown:
public TvshowGridViewModel TvshowGridViewModel
{ get{return _tvshowGridViewModel;} }

Related

Designdata via xaml in WPF VS20019

I have been struggling with trying to make sample data works out of a XAML. I have tried using this guide https://blogs.msdn.microsoft.com/wpfsldesigner/2010/06/30/sample-data-in-the-wpf-and-silverlight-designer/ and this guide https://learn.microsoft.com/en-us/windows/uwp/data-binding/displaying-data-in-the-designer to get information on the subject, but besides those pages, I haven't found any other sources with good enough information. And to try an understand this mode I made a simple WPF project to test it.
<Window x:Class="WpfApp1.MainWindow" 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:WpfApp1" mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800" d:DataContext="{d:DesignData Source=DesignData.xaml}">
<Window.DataContext>
<local:Viewmodel/>
</Window.DataContext>
<Grid>
<TextBlock HorizontalAlignment="Left" Margin="119,104,0,0" TextWrapping="Wrap" Text="{Binding TextBlockValue}" VerticalAlignment="Top"/>
<TextBox HorizontalAlignment="Left" Height="23" Margin="255,101,0,0" TextWrapping="Wrap" Text="{Binding TextboxValue}" VerticalAlignment="Top" Width="120"/>
<Border Margin="542,71,80,223" BorderThickness="2">
<Border.BorderBrush>Black</Border.BorderBrush>
<ItemsControl ItemsSource="{Binding Persons}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding FirstName}"/>
<TextBlock Text="{Binding Lastname}"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Border>
</Grid>
</Window>
This is my simple WPF window that has a textbox, textblock and a ItemsControl. It has a DataContext set with a Viewmodel and a design data DataContext. Viewmodel is as follows:
public class Viewmodel : INotifyPropertyChanged
{
public Viewmodel()
{
Persons = new ObservableCollection<Person>();
Persons.Add(new Person{FirstName = "first one", Lastname = "last one"});
Persons.Add(new Person{FirstName = "John", Lastname = "Doe"});
Persons.Add(new Person{FirstName = "Jane", Lastname = "Doe"});
TextBlockValue = "This is a textBlock";
textboxValue = "This is a textBox";
}
private string textBlockValue;
public string TextBlockValue
{
//<Omitted for readability>
}
private string textboxValue;
public string TextboxValue
{
//<Omitted for readability>
}
public ObservableCollection<Person> Persons { get; set; }
//<Omitted INotifyPropertyChanged implementation for readability>
}
public class Person : INotifyPropertyChanged
{
private string firstName;
public string FirstName
{
//<Omitted for readability>
}
private string lastname;
public string Lastname
{
//<Omitted for readability>
}
public event PropertyChangedEventHandler PropertyChanged;
//<Omitted INotifyPropertyChanged implementation for readability>
}
And my design data:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApp1">
<local:Viewmodel TextboxValue="Box Test" TextBlockValue="Block test" x:Key="Viewmodel">
<local:Viewmodel.Persons>
<local:Person Lastname="test" FirstName="test"/>
<local:Person Lastname="test" FirstName="test"/>
<local:Person Lastname="test" FirstName="test"/>
</local:Viewmodel.Persons>
</local:Viewmodel>
</ResourceDictionary>
When I add my ViewModel datacontext to the xaml I can see that the values show up in the designer. But when I assign my d:datacontext, the test data does not appear as expected. I think it is because my design data is wrong, but I cannot figure out why it is wrong.
The contents of your DesignData.xaml file should look like this, i.e. it shouldn't contain a ResourceDictionary:
<local:Viewmodel xmlns:local="clr-namespace:WpfApp1" TextboxValue="Box Test" TextBlockValue="Block test">
<local:Viewmodel.Persons>
<local:Person Lastname="test" FirstName="test"/>
<local:Person Lastname="test" FirstName="test"/>
<local:Person Lastname="test" FirstName="test"/>
</local:Viewmodel.Persons>
</local:Viewmodel>
You may also want to set the Build Action of the file to DesignData.

Binding DataGridComboBoxColumn to enum value

I'm trying to bind an enum value to DataGridComboBoxColumn, but it does not work. In my case i want to bind the enum CamSegmentType to the DataGridComboBoxColumn. It seems that the enum eCamType could not be found. I don't know what's wrong.
XAML:
<Window x:Class="WpfApp1.MainWindow"
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:lvc="clr-namespace:LiveCharts.Wpf;assembly=LiveCharts.Wpf"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
Title="MainWindow" Height="350" Width="825">
<Window.Resources>
<ObjectDataProvider MethodName="GetValues" ObjectType="{x:Type sys:Enum}" x:Key="GetEnumValues">
<ObjectDataProvider.MethodParameters>
<x:Type TypeName="local:eCamType"/>
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
</Window.Resources>
<Grid>
<DataGrid Name="dgCamSegements" AutoGenerateColumns="False" Margin="10,180,10,10">
<DataGrid.Columns>
<DataGridComboBoxColumn Header="Value" ItemsSource="{Binding Source={StaticResource GetEnumValues}}" SelectedValueBinding="{Binding CamSegmentType}" />
<DataGridTextColumn Header="Leitwert" Binding="{Binding MasterPosStart}" />
<DataGridTextColumn Header="Folgewert" Binding="{Binding SlavePosStart}" />
</DataGrid.Columns>
</DataGrid>
</Grid>
Code:
namespace WpfApp1
{
public partial class MainWindow : Window
{
public enum eCamType { Gerade, Polynom, };
public class CamSegment
{
public eCamType CamSegmentType { get; set; }
public double MasterPosStart { get; set; }
public double SlavePosStart { get; set; }
}
public MainWindow()
{
InitializeComponent();
...
Can anyone help me?
Add in Your Code:
Mode=TwoWay, UpdateSourceTrigger=PropertyChanged in
SelectedValueBinding DataGridComboBoxColumn

How to bind UserControl to ViewModel

I'm having a bit of a trouble connecting both components:
View:
<UserControl x:Class="CoolPlaces.Views.ListItem"
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"
mc:Ignorable="d"
FontFamily="{StaticResource PhoneFontFamilyNormal}"
FontSize="{StaticResource PhoneFontSizeNormal}"
Foreground="{StaticResource PhoneForegroundBrush}"
d:DesignHeight="480" d:DesignWidth="480">
<ListBox ItemsSource="{Binding ListViewModel}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Path=Name, Mode=OneWay}" />
<TextBox Text="{Binding Path=Description, Mode=OneWay}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</UserControl>
Part of Main Page View: in which I include above user control:
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<views:ListItem Height="300" />
</Grid>
ViewModel:
namespace CoolPlaces.ViewModels
{
public class ListViewModel : INotifyPropertyChanged
{
private ObservableCollection<BasicModel> _places;
public ObservableCollection<BasicModel> Places {
get {
return _places;
}
set {
_places = value;
RaisePropertyChanged("Places");
}
}
public ListViewModel() {
Places = new ObservableCollection<BasicModel>(_loadPlaces());
}
private IEnumerable<BasicModel> _loadPlaces() {
return //some hard coded objects
}
}
}
MainPage
namespace CoolPlaces
{
public partial class MainPage : PhoneApplicationPage
{
private ListViewModel vm;
// Constructor
public MainPage()
{
InitializeComponent();
vm = new ListViewModel();
}
}
}
You're close. You need to set the DataContext equal to your ViewModel.
public partial class MainPage : PhoneApplicationPage
{
private ListViewModel vm;
// Constructor
public MainPage()
{
InitializeComponent();
DataContext = vm = new ListViewModel();
}
}
Then your ListBox doesn't need to be bound to it. Instead, bind it to your Places property on your ViewModel:
<ListBox ItemsSource="{Binding Places}">

Accessing methods of a control that is in a Content Template

Relative WPF new-comer, this will probably have a simple solution (I hope!). I have a class with two properties:
public class MyClass
{
public String Name { get; set; }
public String Description { get; set; }
}
I have a user control which has a textblock and a button: the textblock displays text (obviously) and the button is used to either bold or unbold the text of the text block:
MyControl.xaml:
<UserControl
x:Class="WpfApplication1.MyControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="300"
xmlns:this="clr-namespace:WpfApplication1">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="48" />
</Grid.ColumnDefinitions>
<TextBlock
x:Name="txtDescription"
Grid.Column="0"
Text="{Binding Path=Description, RelativeSource={RelativeSource AncestorType={x:Type this:MyControl}}}" />
<Button
x:Name="btnBold"
Grid.Column="1"
Content="Bold"
Click="btnBold_Click" />
</Grid>
</UserControl>
MyControl.xaml.cs:
public partial class MyControl : UserControl
{
public static readonly DependencyProperty DescriptionProperty = DependencyProperty.Register("Description", typeof(String), typeof(MyControl));
public String Description
{
get { return GetValue(MyControl.DescriptionProperty) as String; }
set { SetValue(MyControl.DescriptionProperty, value); }
}
public MyControl()
{
InitializeComponent();
}
private void btnBold_Click(object sender, RoutedEventArgs e)
{
ToggleBold();
}
public void ToggleBold()
{
if (txtDescription.FontWeight == FontWeights.Bold)
{
btnBold.Content = "Bold";
txtDescription.FontWeight = FontWeights.Normal;
}
else
{
btnBold.Content = "Unbold";
txtDescription.FontWeight = FontWeights.Bold;
}
}
}
In my MainWindow I have a tab control which has an item template (to display MyClass.Name in the header of each tab) and a content template. The content template contains one of my above controls and MyClass.Description is bound to MyControl.Description:
MainWindow.xaml:
<Window
x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow"
Height="350"
Width="525"
xmlns:this="clr-namespace:WpfApplication1">
<Grid>
<TabControl x:Name="tabItems">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<this:MyControl
Description="{Binding Description}" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>
</Window>
MainWindow.xaml.cs:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
List<MyClass> myClasses = new List<MyClass>();
myClasses.Add(new MyClass() { Name = "My Name", Description = "My Description" });
myClasses.Add(new MyClass() { Name = "Your Name", Description = "Your Description" });
tabItems.ItemsSource = myClasses;
}
}
When the program runs I add two objects of type MyClass to a List, set the list to the ItemsSource property of the tab control and it all works perfectly: I get two tabs with "My Name" and "Your Name" as the headers, the description is shown in the correct place and the button turns the bold on or off correctly.
My question is this, how do I add a button OUTSIDE of the tab control which could call the MyControl.ToggleBold method of the MyControl object which is in the content template of the selected item:
MainWindow.xaml:
<Window
x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow"
Height="350"
Width="525"
xmlns:this="clr-namespace:WpfApplication1">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TabControl x:Name="tabItems" Grid.Row="0">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<this:MyControl
x:Name="myControl"
Description="{Binding Description}"/>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
<Button Grid.Row="1" Content="Toggle Selected Tab" Click="Button_Click" />
</Grid>
</Window>
MainWindow.xaml.cs:
...
private void Button_Click(object sender, RoutedEventArgs e)
{
MyClass myClass = tabItems.SelectedItem as MyClass;
MyControl myControl;
///get the instance of myControl that is contained
///in the content template of tabItems for the
///myClass item
myControl.ToggleBold();
}
...
I know I can access the data template by calling tabItems.SelectedContentTemplate but as far as I can tell I cannot access controls within the template (and I don't think I should be doing that either). There is the FindName method but I don't know pass as the templatedParent parameter.
Any help would be hugely appreciated.
You can navigate the VisualTree to find the control you're looking for.
For example, I use a set of custom VisualTreeHelpers which would allow me to call something like this:
var myControl = VisualTreeHelpers.FindChild<MyControl>(myTabControl);
if (myControl != null)
myControl.ToggleBold();

Interesting Issue with Silverlight Datagrid

Folks,
I'm having an interesting issue with Silverlight DataGrid data binding. It may be b/c I'm not binding the data source properly. Here's the object & the observable collection
/// <summary>
/// Interface for all model elements
/// </summary>
public interface IBaseModel
{
}
/// <summary>
/// Employee model
/// </summary>
public class EmployeeModel : IBaseModel
{
public string FirstName { get; set; }
public string LastName { get; set; }
public override string ToString()
{
return FirstName + LastName;
}
}
// The observable collection is loaded and bound in the user control
public partial class EmployeeMasterDetailsWindow : UserControl
{
public EmployeeMasterDetailsWindow()
{
try
{
InitializeComponent();
ObservableCollection<IBaseModel> k = new ObservableCollection<IBaseModel>()
{new EmployeeModel(){FirstName="Frodo",
LastName=" Baggins"},
new EmployeeModel(){FirstName="Pippin",
LastName="Thomas"},
new EmployeeModel(){FirstName="John",
LastName="Doe"},
new EmployeeModel(){FirstName="Tim",
LastName="Kiriev"}};
dataGrid1.DataContext = k;
CustomersListBox.DataContext = k;
}
catch (Exception ex)
{
}
}
}
//here's the XAML
<UserControl x:Class="AdventureWorksManagement.UI.EmployeeMasterDetailsWindow"
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"
mc:Ignorable="d"
d:DesignHeight="379" d:DesignWidth="516"
xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"
xmlns:toolkit="http://schemas.microsoft.com/winfx/2006/xaml/presentation/toolkit">
<UserControl.Resources>
<DataTemplate x:Key="CustomerTemplate">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding FirstName}" />
<TextBlock Text=" " />
<TextBlock Text="{Binding LastName}" />
</StackPanel>
</DataTemplate>
</UserControl.Resources>
<Grid x:Name="LayoutRoot" Background="White" Height="371" Width="595">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="312*" />
<ColumnDefinition Width="283*" />
</Grid.ColumnDefinitions>
<sdk:DataGrid Height="325" HorizontalAlignment="Left"
Margin="12,12,0,0" Name="dataGrid1" VerticalAlignment="Top" Width="271" ItemsSource="{Binding}"
RowDetailsTemplate="{StaticResource CustomerTemplate}">
</sdk:DataGrid>
<ListBox x:Name="CustomersListBox"
Margin="10,10,10,11"
ItemsSource="{Binding}"
ItemTemplate="{StaticResource CustomerTemplate}" />
</Grid>
The Listbox shows all the of the employees, but the DataGrid doesn't. I don't even see the DataGrid. I see this error message in the output window:
'System.Collections.ObjectModel.ObservableCollection1[AdventureWorksManagement.Model.IBaseModel]'
'System.Collections.ObjectModel.ObservableCollection1[AdventureWorksManagement.Model.IBaseModel]'
(HashCode=54025633).
BindingExpression: Path='FirstName'
DataItem='System.Collections.ObjectModel.ObservableCollection`1[AdventureWorksManagement.Model.IBaseModel]'
(HashCode=54025633); target element is
'System.Windows.Controls.TextBlock'
(Name=''); target property is 'Text'
(type 'System.String')..
What could I be doing wrong?
By making it an ObservableCollection<IBaseModel> you are effectively casting all the child objects to IBaseModel, which has no members.
In this instance make it an ObservableCollection<EmployeeModel>.

Resources