Design time ItemsSource on ItemsControl - c#

I'm trying to design the DataTemplate for my ItemsControl and I need some mock data to populate the template. I read using d:DataContext is enough so that I don't have to create a mock class. How can I do this?

The instance you have to use with d:DataContext must be declared in the XAML, with a StaticResource for example.
Here is how you could do it:
<UserControl x:Class="WpfApplication1.UserControl1"
xmlns:local="clr-namespace:WpfApplication1"
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">
<UserControl.Resources>
<local:MyViewModel x:Key="mockViewModel"/>
</UserControl.Resources>
<Grid>
<ItemsControl d:DataContext="{StaticResource mockViewModel}"
ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</UserControl>
The class I used as data context is defined as follows:
namespace WpfApplication1
{
public class Item
{
public Item(string name)
{
Name = name;
}
public string Name { get; private set; }
}
public class MyViewModel
{
public List<Item> Items
{
get
{
return new List<Item>() { new Item("Thing 1"), new Item("Thing 2") };
}
}
}
}
Of course, you can also set the data context on the UserControl or on your Window.
Here's the result:

Related

Adding objects to WPF Panel displaying buttons

I want to add objects dynamicly to a ObservationCollection, which then should add buttons with a Content (value) of a field of the object to a panel.
App.xaml.cs
using System.Windows;
namespace WpfApp1
{
public partial class App : Application
{
}
}
MainWindow.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"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<ItemsControl Name="dashboardList">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding Name}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Window>
DashboadViewModel.cs
using System.Collections.ObjectModel;
namespace WpfApp1
{
public class DashboardViewModel
{
public ObservableCollection<Dashboard> Dashboards { get; set; }
public DashboardViewModel()
{
LoadDashboards();
}
public void LoadDashboards()
{
ObservableCollection<Dashboard> dashboards = new ObservableCollection<Dashboard>();
dashboards.Add(new Dashboard { Name = "Dashboard1" });
dashboards.Add(new Dashboard { Name = "Dashboard2" });
Dashboards = dashboards;
}
}
}
Dashboard.cs
namespace WpfApp1
{
public class Dashboard
{
public string Name;
}
}
How do I get to create the buttons, am I on the right track with the ItemControl?
public string Name should be changed to be a public property to support data binding:
public class Dashboard
{
public string Name { get; set; }
}
Besides that, an instance of DashboardViewModel should be assigned to the Window's DataContext property and the ItemsControl's ItemsSource property should be bound like this:
<Window ...
xmlns:local="clr-namespace:WpfApp1">
<Window.DataContext>
<local:DashboardViewModel/>
</Window.DataContext>
<Grid>
<ItemsControl ItemsSource="{Binding Dashboards}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding Name}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Window>
If necessary, you can access the view model instance in the Window's code behind like this:
var vm = (DashboardViewModel)DataContext;
vm.LoadDashboards();

Wpf binding collection property in UserControl (xaml)

Add collections of buttons in my userControl (Options).
In xaml disigner the are displayed.
Output
When i run my application:
If Options not initialized, then an error XamlObjectWriterException: Property collection "WpfAppUserControl.Buttons"."Options" (null).
If Options = new List(), then window without buttons
MainWindow.xaml
<Window x:Class="WpfAppUserControl.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:WpfAppUserControl"
mc:Ignorable="d"
Title="MainWindow" Height="250" Width="300">
<Grid>
<local:Buttons x:Name="Buttons"
VerticalAlignment="Center"
HorizontalAlignment="Center"
HorizontalContentAlignment="Center">
<local:Buttons.Options>
<Button Content="Agudabi 1" Height="20" Margin="2" />
<Button Content="Agudabi 2" Height="20" Margin="2" />
<Button Content="Agudabi 3" Height="20" Margin="2" />
</local:Buttons.Options>
</local:Buttons>
</Grid>
</Window>
Buttons.xaml
<UserControl x:Class="WpfAppUserControl.Buttons"
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"
xmlns:local="clr-namespace:WpfAppUserControl"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<WrapPanel x:Name="InternalContainer" Orientation="Horizontal" HorizontalAlignment="Center"/>
</Grid>
</UserControl>
Buttons.xaml.cs
#region Usings
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
#endregion
namespace WpfAppUserControl
{
public partial class Buttons : UserControl
{
public Buttons()
{
//Options = new ObservableCollection<Button>();
InitializeComponent();
}
public ObservableCollection<Button> Options
{
get { return (ObservableCollection<Button>) GetValue(OptionsProperty); }
set { SetValue(OptionsProperty, value); }
}
public static readonly DependencyProperty OptionsProperty =
DependencyProperty.Register(nameof(Options), typeof(ObservableCollection<Button>), typeof(Buttons),
new PropertyMetadata(/*new ObservableCollection<Button>()*/, PropertyChangedCallback));
private static void PropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var obj = d as Buttons;
foreach (var button in obj.Options)
{
obj.InternalContainer.Children.Add(button);
}
}
}
}
Here is an example of what you should actually do.
Instead of a UserControl with a collection property, use an ItemsControl like this:
<ItemsControl ItemsSource="{Binding Options}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding Name}" Command="{Binding Command}"
Height="20" Margin="2"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Then create a view model with a collection of data items with properties for the Button Content and Command:
public class ViewModel
{
public ObservableCollection<Option> Options { get; }
= new ObservableCollection<Option>();
}
public class Option
{
public string Name { get; set; }
public ICommand Command { get; set; }
}
Initialize it like shown below, where the ICommand implementation is ommited for brevity. Search the web for RelayCommand for a the implementation details.
public MainWindow()
{
InitializeComponent();
var vm = new ViewModel();
vm.Options.Add(new Option { Name = "Agudabi 1" });
vm.Options.Add(new Option { Name = "Agudabi 2" });
vm.Options.Add(new Option { Name = "Agudabi 3" });
DataContext = vm;
}
first initialize OptionsProperty with an empty ObservableCollection:
public partial class Buttons : UserControl
{
public Buttons()
{
Options = new ObservableCollection<Button>();
InitializeComponent();
}
public ObservableCollection<Button> Options
{
get { return (ObservableCollection<Button>) GetValue(OptionsProperty); }
set { SetValue(OptionsProperty, value); }
}
public static readonly DependencyProperty OptionsProperty =
DependencyProperty.Register("Options", typeof(ObservableCollection<Button>), typeof(Buttons));
}
Clemens commented that "Never set the default value of a collection type DP to anything else than null. Otherwise all instances of the UserControl class will operate on the same default collection instance." The same is true about any reference type. So property initialization is done in constructor.
you can do without PropertyChangedCallback, because it is possible to display collection more effectively using ItemsControl:
<UserControl x:Name="myUC" x:Class="WpfAppUserControl.Buttons"
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"
xmlns:local="clr-namespace:WpfAppUserControl"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<ItemsControl ItemsSource="{Binding Path=Options, ElementName=myUC}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel x:Name="InternalContainer" Orientation="Horizontal" HorizontalAlignment="Center"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Grid>
</UserControl>
note that PropertyChangedCallback is not triggered when you add items to collections because collection property itself hasn't changed, it is the same reference

WPF controls not showing up in ItemTemplate(Listbox/ListView)

I have recently started with XAML and WPF. I just created a new project in wpf and add the below XAML code. But...none of the items which are added inside "Listbox.ItemTemplate" or "ListView.ItemTemplate" for that matter show up in designer window. what am i doing wrong? This is a fresh project and so no code-behind stuff added yet. i scratched my head for 15 mins with this but with no success. Please help
<Window x:Class="WpfApplication3.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">
<Grid Margin="10">
<ListBox Margin="10">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Name: " />
<TextBlock Text="Age: " />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Window>
You should bind your ListBox or entire Window to some DataContext (usually this is viewmodel with the data you need to display) or specify items of the list explicitly.
In your snippet you specified only an item template, not the items itself.
The example of XAML-defined items (simple strings):
<Window x:Class="WpfApplication3.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">
<Grid Margin="10">
<ListBox Margin="10">
<ListBox.Items>
<ListBoxItem>123</ListBoxItem>
<ListBoxItem>456</ListBoxItem>
</ListBox.Items>
</ListBox>
</Grid>
</Window>
The example with DataContext and Bindings.
XAML:
<Window x:Class="WpfApplication3.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">
<Grid Margin="10">
<ListBox Margin="10" ItemsSource="{Binding Path=Persons}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Label>Name:</Label><TextBlock VerticalAlignment="Center" Text="{Binding Path=Name}" />
<Label>Age:</Label><TextBlock VerticalAlignment="Center" Text="{Binding Path=Age}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Window>
Codebehind:
namespace WpfApplication3
{
public class PersonViewModel
{
public PersonViewModel(string name, int age)
{
this.name = name;
this.age = age;
}
public string Name
{
get { return name; }
}
private string name;
public int Age
{
get { return age; }
}
private int age;
}
public class MainViewModel
{
public MainViewModel()
{
persons = new ObservableCollection<PersonViewModel>()
{
new PersonViewModel("Lez", 146),
new PersonViewModel("Binja", 158),
new PersonViewModel("Rufus the Destroyer", 9000)
};
}
public ObservableCollection<PersonViewModel> Persons
{
get { return persons; }
}
private ObservableCollection<PersonViewModel> persons;
}
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new MainViewModel();
}
}
}
IMPORTANT: Don't forget to properly implement INotifyPropertyChanged in case of mutable properties of viewmodels (for example, if you will have setters for "Name" and "Age" properties of PersonViewModel).
You don't see any items because your ListBox doesn't contain any data in the designer view. To populate it, a list should be binded to the property "ItemsSource" of your ListBox, or adding datas directly to the property "Items" (XAML or code behind).
If you want to show items in the designer view properly, you should have a ViewModel binding to the DataContext of your page and create a "Sample data" file (via Blend for example).
Item Templates are basically used to show customize data. Just Use simple string items first. It will show in list box.

Setting the DataContext to the current code-behind object in XAML

I'm trying to set the DataContext for my UserControl to the code-behind class of the UserControl. It's really easy to do from the code-behind side:
public partial class OHMDataPage : UserControl
{
public StringList Stuff { get; set; }
public OHMDataPage ()
{
InitializeComponent();
DataContext = this;
}
}
<UserControl 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"
x:Class="LCDHardwareMonitor.Pages.OHMDataPage">
<ScrollViewer>
<ListBox ItemsSource="{Binding Stuff}" />
</ScrollViewer>
</UserControl>
But how can I do this purely from the XAML side and at the UserControl level? It works on child nodes if I do this (and remove DataContext = this; from the code-behind):
<UserControl 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"
x:Class="LCDHardwareMonitor.Pages.OHMDataPage">
<ScrollViewer
DataContext="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UserControl}}">
<ListBox ItemsSource="{Binding Stuff}" />
</ScrollViewer>
</UserControl>
I'd really like to understand how to do this on the UserControl itself. I expected this to work:
<UserControl 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"
x:Class="LCDHardwareMonitor.Pages.OHMDataPage"
DataContext="{Binding RelativeSource={RelativeSource Mode=Self}}">
<ScrollViewer>
<ListBox ItemsSource="{Binding Stuff}" />
</ScrollViewer>
</UserControl>
but it doesn't.
DataContext="{Binding Mode=OneWay, RelativeSource={RelativeSource Self}}"
Should work.
But if your properties are not set before InitializeComponent() is called, the WPF binding mechanism doesn't know that your property's values are changed.
To give you a quick idea:
// the binding should work
public StringList Stuff { get; set; }
public Constructor()
{
Stuff = new StringList { "blah", "blah", "foo", "bar" };
InitializeComponent();
}
// the binding won't work
public StringList Stuff { get; set; }
public Constructor()
{
InitializeComponent();
Stuff = new StringList { "blah", "blah", "foo", "bar" };
}
If you're using a list of strings, consider using an ObservableCollection instead. This will notify the WPF binding mechanism when items are added or removed.

Resharper dosn't recognize the correct ViewModel Type

We are using Resharper and of course we want to take advantage of Resharper's xaml intellisense.
Our View's Data Context are bound to a CurrentViewmodel property of type ViewModelBase. At runtime this Property is set with a View model inheritating from ViewModelBase.
I already added those lines in the View model to set the correct Type:
xmlns:vms="clr-namespace:PQS.ViewModel.Report"
d:DataContext="{d:DesignInstance vms:ReportFilterViewModel, IsDesignTimeCreatable=False}"
But Resharper still keeps looking in ViewModelbase for the Properties.
What else can i try?
Some more Code:
Setting the DataContext:
<UserControl.DataContext>
<Binding Path="ReportMainViewModel.CurrentVm" Source="{StaticResource Locator}"/>
</UserControl.DataContext>
Binding Something (Products is a Property on ReportFilterViewmodel, r# keeps looking for it in ViewModelBase):
<ListBox ItemsSource="{Binding Products.View}" Background="White" DisplayMemberPath="Name.ActualTranslation">
</ListBox>
R# can't statically find concrete view model type that will be available in runtime, so you need to annotate data context type manually like this:
using System.Collections.Generic;
public partial class MainWindow {
public MainWindow() {
Current = new ConcreteViewModel {
Products = {
new Product(),
new Product()
}
};
InitializeComponent();
}
public ViewModelBase Current { get; set; }
}
public class ViewModelBase { }
public class ConcreteViewModel : ViewModelBase {
public ConcreteViewModel() {
Products = new List<Product>();
}
public List<Product> Products { get; private set; }
}
public class Product {
public string ProductName { get { return "Name1"; } }
}
And XAML part:
<Window x:Class="MainWindow" x:Name="MainWin"
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:global="clr-namespace:" mc:Ignorable="d"
DataContext="{Binding ElementName=MainWin, Path=Current}">
<!-- here the type of data context is ViewModelBase -->
<Grid d:DataContext="{d:DesignInstance global:ConcreteViewModel}">
<!-- and here is ConcreteViewModel -->
<ListBox ItemsSource="{Binding Path=Products}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ProductName}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Window>
Or like this:
<Window x:Class="MainWindow" x:Name="MainWin"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:global="clr-namespace:"
DataContext="{Binding ElementName=MainWin, Path=Current}">
<Grid>
<ListBox ItemsSource="{Binding Path=(global:ConcreteViewModel.Products)}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ProductName}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Window>

Categories