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();
Related
I would to create application with TabControl.
I would to put UserControls on Tabs, but I wouldn't create this in *.cs file, but using binding.
I have application where I can add tab with usercontrol, but i don't know how bind String with tab name.
My code:
MainWindow.xaml.cs
using System.Windows;
namespace WpfApp3
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
}
MainWindow.xaml
<Window x:Class="WpfApp3.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:WpfApp3"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TabControl ItemsSource="{Binding UserControls}">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="???" /> <!--binding? -->
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>
<Button Grid.Row="1" Content="Dodaj zakładkę" Command="{Binding AddButtonCommand}"/>
</Grid>
</Window>
MainWindowViewModel.cs
using Prism.Commands;
using Prism.Mvvm;
using System.Collections.ObjectModel;
using System.Windows.Controls;
using System.Windows.Input;
namespace WpfApp3
{
class MainWindowViewModel : BindableBase
{
public ObservableCollection<UserControl> UserControls { get; set; }
public ICommand AddButtonCommand { get; set; }
public MainWindowViewModel()
{
AddButtonCommand = new DelegateCommand(ClickButton);
UserControls = new ObservableCollection<UserControl>();
}
private void ClickButton()
{
UserControls.Add(new UCTest());
RaisePropertyChanged("UserControls");
}
}
}
I tried add constructor UserControl with parameter with name, but i don't know how join it with binding.
My second thing is create class with UserControl and String (with tab name), but i couldnt bind field with UserControl to Widdow.
Thanks,
regards.
You shouldn't add UserControls or any other UI elements to the ObservableCollection in the view model. Instead you should define your own model type and add instances of this one to the source collection.
You could then bind to any property of this class:
class Model
{
public string Header { get; set; }
}
class MainViewModel : BindableBase
{
public ObservableCollection<Model> UserControls { get; set; }
public ICommand AddButtonCommand { get; set; }
public Window13ViewModel()
{
AddButtonCommand = new DelegateCommand(ClickButton);
UserControls = new ObservableCollection<Model>();
}
private void ClickButton()
{
UserControls.Add(new Model() { Header = "some name..." });
}
}
XAML:
<TabControl ItemsSource="{Binding UserControls}">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Header}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<local:UCTest />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
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
I have a situation where the parent DataContext is different from the child DataContext, and I would like to access the parent DataContext from a binding in the child. This can be done using a verbose RelativeSource like so:
<Button Content="{Binding RelativeSource={RelativeSource AncestorType={x:Type local:MainWindow}}, Path=DataContext.Bar}"/>
I would like to find a way to reference the parent's DataContext in a much more concise way. Is there a way, for example, the parent could expose a reference to it's DataContext (or any of it's properties for that matter) through a Resource defined in the parent? Ideally, the child's binding would then look something like the following (forgive me for using StaticResource as an example).
<Button Path=Bar, Content="{StaticResource parentDataContextReference}"/>
Ideally, avoiding code-behind, but open to that solution. A contrived example:
MainWindow.xaml
<Window x:Class="BindingTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:BindingTest"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<x:ArrayExtension x:Key="children" Type="{x:Type local:ChildViewModel}">
<local:ChildViewModel Name="Child 1"/>
<local:ChildViewModel Name="Child 2"/>
</x:ArrayExtension>
</Window.Resources>
<StackPanel>
<Button Content="{Binding Foo}" Height="20" Width="60"></Button>
<ListView ItemsSource="{StaticResource children}">
<ListView.ItemTemplate>
<DataTemplate>
<local:ChildView/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
</Window>
MainWindow.xaml.cs
namespace BindingTest
{
public class MainViewModel
{
public string Foo { get; set; }
public string Bar { get; set; }
public MainViewModel()
{
Foo = "Foo";
Bar = "Bar";
}
}
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MainViewModel();
}
}
}
ChildView.xaml
<UserControl x:Class="BindingTest.ChildView"
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:BindingTest"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<StackPanel Orientation="Horizontal">
<Button Content="{Binding Name}"/>
<Button Content="{Binding RelativeSource={RelativeSource AncestorType={x:Type local:MainWindow}}, Path=DataContext.Bar}"/>
</StackPanel>
</UserControl>
ChildView.xaml.cs
namespace BindingTest
{
public class ChildViewModel
{
public string Name { get; set; }
public ChildViewModel()
{
Name = "Undefined";
}
}
public partial class ChildView : UserControl
{
public ChildView()
{
InitializeComponent();
}
}
}
to long for a comment... i use RelativeSource Binding too. but i use it with "Marker Interfaces". this means simply empty interfaces which i'm putting on the Views/UserControls.
public interface IMainWindowMarker {}
public Window MainWindow : IMainWindowMarker {}
the binding the looks much the same to yours but its more readable to me.
<Button Content="{Binding RelativeSource={RelativeSource AncestorType={x:Type local:IMainWindowMarker}}, Path=DataContext.Bar}"/>
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:
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>