Add/Remove Tabs with User Control Dynamically in WPF - Binding Tab Name - c#

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>

Related

In WPF MVVM, i have a usercontrol with a check box, how do i connect a command to the viewmodel

I am still new to WPF and MVVM and am trying to keep the seperation between View and View Model.
i have an app, essentially a projects task list app, in this i create projects and within each project i can create a set of tasks. Most is working well, but essentially i cannot get a command binding on a checkbox in a user control to work using DP, inherited datacontext etc. i always ge a binding failed error when running the app. i am trying to bing to a command in the viewmodel of the view which contains the user controls.
i created a user control to pull the task data together in the view, the command is on the checkbox
<UserControl x:Class="TaskProjectApp.Controls.TaskControl"
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:TaskProjectApp.Controls"
mc:Ignorable="d"
d:DesignHeight="100" d:DesignWidth="300">
<Grid Background="LightBlue">
<StackPanel Margin="5,5,5,5">
<TextBlock x:Name="titleTB"
Text="title"
FontSize="20"
FontWeight="Bold"/>
<TextBlock x:Name="DescriptionTB"
Text="description.."
FontSize="15"
Foreground="DodgerBlue"/>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<TextBlock x:Name="priority"
Text="0"
FontSize="15"
FontStyle="Italic"/>
<CheckBox Grid.Column="1"
x:Name="iscomplete"
Command="{Binding SetComplete}"/>
</Grid>
</StackPanel>
</Grid>
</UserControl>
in the user control code behind i have set the DP and the set text function is working
namespace TaskProjectApp.Controls
{
/// <summary>
/// Interaction logic for TaskControl.xaml
/// </summary>
public partial class TaskControl : UserControl
{
public UserTask Task
{
get { return (UserTask)GetValue(TaskProperty); }
set { SetValue(TaskProperty, value); }
}
// Using a DependencyProperty as the backing store for Task. This enables animation, styling, binding, etc...
public static readonly DependencyProperty TaskProperty =
DependencyProperty.Register("Task", typeof(UserTask), typeof(TaskControl), new PropertyMetadata(new UserTask()
{
Title = "title",
Description = "none",
Comments = "none"
}, SetText));
private static void SetText(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
TaskControl task = d as TaskControl;
if (task != null)
{
task.titleTB.Text = (e.NewValue as UserTask).Title;
task.DescriptionTB.Text = (e.NewValue as UserTask).Description;
task.priority.Text = (e.NewValue as UserTask).Priority.ToString();
task.iscomplete.IsChecked = (e.NewValue as UserTask).IsComplete;
}
}
public TaskControl()
{
InitializeComponent();
}
}
}
now to make this work i set the binding of the user control in the window as so, the listview takes the usercontrols and implements the observable collection of tasks.
<Window x:Class="TaskProjectApp.Views.ProjectsView"
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:TaskProjectApp.Views"
xmlns:uc="clr-namespace:TaskProjectApp.Controls"
mc:Ignorable="d"
Title="ProjectsView" Height="450" Width="800">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<uc:ProjectControl Project="{Binding UserProject}" />
<StackPanel Grid.Row="1">
<TextBlock Text="Task List"/>
<ListView ItemsSource="{Binding Tasks}"
SelectedItem="{Binding SelectedTask}">
<ListView.ItemTemplate>
<DataTemplate>
<uc:TaskControl Task="{Binding}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Button Content="Add Task"
Command="{Binding NewProjectTask}"/>
<Button Content="Delete Task"
Command="{Binding DeleteProjectTask}"/>
</StackPanel>
</Grid>
</Window>
this seems to completely stop me using the command, i set the datacontext in the code behind, to the whole window
public partial class ProjectsView : Window
{
public ProjectViewModel ProjectViewModel { get; set; }
public ProjectsView()
{
InitializeComponent();
}
public ProjectsView(UserProject userProject)
{
InitializeComponent();
ProjectViewModel = new ProjectViewModel(userProject);
DataContext = ProjectViewModel;
}
}
and reading trying to solve this has shown that the usercontrol should inherit the datacontext of the parent window.
i have seen solutions using relative paths and DPs for the commands as well as people saying these are not needed just let the inherited datacontext handle it.
but i have tried all three an neither works.
the interface shows me a message box saying no datacontext found, although i notice this is the case when you set the datacontext in code behind and not the xaml.
the SetCommand is created in the projects view model and its a property not a field as i have seen this fail for that reason too.
namespace TaskProjectApp.ViewModels
{
public class ProjectViewModel
{
public UserProject UserProject { get; set; }
public ProjectViewModel(UserProject userProject)
{
UserProject = userProject;
Tasks = new ObservableCollection<UserTask>();
NewProjectTask = new NewProjectTaskCommand(this);
DeleteProjectTask = new DeleteProjectTaskCommand(this);
SetComplete = new SetCompleteCommand();
ReadTaskDatabase();
}
public ObservableCollection<UserTask> Tasks { get; set; }
public NewProjectTaskCommand NewProjectTask { get; set; }
public DeleteProjectTaskCommand DeleteProjectTask { get; set; }
public SetCompleteCommand SetComplete { get; set; }
public UserTask SelectedTask { get; set; }
public void ReadTaskDatabase()
{
List<UserTask> list = new List<UserTask>();
using (SQLiteConnection newConnection = new SQLiteConnection(App.databasePath))
{
newConnection.CreateTable<UserTask>();
list = newConnection.Table<UserTask>().ToList().OrderBy(c => c.Title).ToList();
}
Tasks.Clear();
foreach (UserTask ut in list)
{
if (ut.ProjectId == UserProject.Id)
{
Tasks.Add(ut);
}
}
}
}
}
if anyone can point out where i am going wrong tat will be great as i fear i am now not seeing the wood for the trees.
I found the solution thanks to Ash link Binding to Window.DataContext.ViewModelCommand inside a ItemsControl not sure how i missed it, maybe wrong key words. anyway because the datacontext of the usercontrol is being made into my data class in the observable list Tasks
<StackPanel Grid.Row="1">
<TextBlock Text="Task List"/>
<ListView ItemsSource="{Binding Tasks}"
SelectedItem="{Binding SelectedTask}">
<ListView.ItemTemplate>
<DataTemplate>
<uc:TaskControl Task="{Binding}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Button Content="Add Task"
Command="{Binding NewProjectTask}"/>
<Button Content="Delete Task"
Command="{Binding DeleteProjectTask}"/>
</StackPanel>
you need to use a relative path inside the user control to look up past the ItemTemplate to the ListView itself as this uses the viewmodel data context to bind to, so has access to the right level
<UserControl x:Class="TaskProjectApp.Controls.TaskControl"
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:TaskProjectApp.Controls"
mc:Ignorable="d"
d:DesignHeight="100" d:DesignWidth="300">
<Grid Background="LightBlue">
<StackPanel Margin="5,5,5,5">
<TextBlock x:Name="titleTB"
Text="title"
FontSize="20"
FontWeight="Bold"/>
<TextBlock x:Name="DescriptionTB"
Text="description.."
FontSize="15"
Foreground="DodgerBlue"/>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<TextBlock x:Name="priority"
Text="0"
FontSize="15"
FontStyle="Italic"/>
<CheckBox Grid.Column="1"
x:Name="iscomplete"
Command="{Binding DataContext.SetComplete, RelativeSource={RelativeSource AncestorType=ListView}}"/>
</Grid>
</StackPanel>
</Grid>
</UserControl>
this might be limiting in future as it measn the usercontrol will look for a listview to bind the command, but it solves the immediate issue.

In a WPF application using Visual Studio, how can I bind a usercontrol to a usercontrol variable?

I'm not sure if I asked the question correctly, because I am not finding the answer by searching the internet. I am creating a wizard window. I have a window that has a title at the top and buttons at the bottom that will stay there throughout changing the pages. So in the xaml.cs for the window, I have a list of UserControls that will contain all of the views for the wizard. I also have a property/field that holds the current view. I want to create a xaml UserControl tag that binds to the current view property. It should change when I change the current view property (I have already implemented the INotifyChanged interface). The current view property will be changed by c#. Here is the code that I have(simplified to show whats needed), and when I run it nothing shows in the view area:
WizardWindow.xaml:
<Window x:Class="WizardWindow.WizardWindow"...>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="70"/>
<RowDefinition Height="*"/>
<RowDefinition Height="50"/>
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<TextBlock Text="Wizard Title"/>
</Grid>
<Grid Grid.Row="1">
<Border Name="WizardWindowPageContent" Margin="5" BorderBrush="Black" BorderThickness="1">
<!--This is what I have tried but isn't working -->
<UserControl Content="{Binding CurrentView}" />
</Border>
</Grid>
<Grid Grid.Row="2" Name="WizardButtons">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Button Grid.Column="0">Cancel</Button>
<Button Grid.Column="2">Back</Button>
<Button Grid.Column="3">Next</Button>
</Grid>
</Grid>
</Window>
WizardWindow.xaml.cs:
using System;
...
using System.Windows.Controls;
namespace WizardWindow
{
public partial class WizardWindow : Window, INotifyPropertyChanged
{
// List of views in the config window
private List<UserControl> views;
// Current view showing in the window
private UserControl currentView;
public UserControl CurrentView
{
get { return currentView; }
set
{
currentView = value;
OnPropertyChanged("CurrentView");
}
}
// Used to keep track of the view index
private int viewIndex;
public WizardWindow ()
{
InitializeComponent();
// Set the screen to the center of the screen
WindowStartupLocation = System.Windows.WindowStartupLocation.CenterScreen;
views = new List<UserControl>();
views.Add(new FirstWizardPage(this));
viewIndex = 0;
CurrentView = views[viewIndex];
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string info)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(info));
}
}
}
}
FirstWizardPage.xaml:
<!-- This should show up in the window -->
<UserControl x:Class="WizardWindow.FirstWizardPage" ... >
<Grid>
<TextBlock>Lorem Ipsum ...</TextBlock>
</Grid>
</UserControl>
FirstWizard.xaml.cs:
using System.Windows.Controls;
namespace WizardWindow
{
public partial class FirstWizardPage : UserControl
{
public FirstWizardPage(WizardWindow window)
{
InitializeComponent();
}
}
}
The given possible duplicate, Window vs Page vs UserControl for WPF navigation?
, is a good solution if I wanted to rewrite my program. However, it is not a solution to my exact problem. Someone else might have a similar problem and need this solution.
you need to use this:
<ContentControl x:Name="MyView"
Content="{Binding CurrentView}" />
It should work for you, but it's not MVVM.
You must binding on property DataContext of your user control, if you want to use MVVM.
I Will show little example for you:
It is MainWindow.xaml:
<Window x:Class="WpfApp2.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:WpfApp2"
xmlns:cefSharp="clr-namespace:CefSharp.Wpf;assembly=CefSharp.Wpf"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<local:Foo DataContext="{Binding UserControlViewModel}"/>
</Grid>
It is MainWindow.xaml.cs:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MainWindowViewModel();
}
}
It is MainWindowViewModel.cs:
public class MainWindowViewModel
{
public MainWindowViewModel()
{
UserControlViewModel = new UserControlViewModel{ Name = "Hello World" };
}
public UserControlViewModel UserControlViewModel { get; }
}
It is Foo.xaml:
<UserControl x:Class="WpfApp2.Foo"
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:WpfApp2"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<TextBlock Text="{Binding Name}"/>
</Grid>
It is FooUsercontrolViewModel.cs
public class FooUserControlViewModel
{
public string Name { get; set; }
}

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

How to bind a property in the view model of a usercontrol to a property in another usercontrol

I am currently working within a WPF user control using MVVM. My MainWindow.xaml looks like below.
MainWindow.xaml
<Window.Resources>
<ObjectDataProvider x:Key="TabsList" ObjectType="{x:Type local:MainWindowModel}" MethodName="GetTabs"/>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ListBox Grid.Column="0" ItemsSource="{Binding Source={StaticResource TabsList}}" IsSynchronizedWithCurrentItem="True">
<ListBox.ItemTemplate>
<DataTemplate>
<Label Content="{Binding Path=TabName}" Margin="10"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<ContentControl Grid.Column="1" Content="{Binding Source={StaticResource TabsList}, Path=MyUserControl}"/>
</Grid>
Data provider class is as below
public class MainWindowModel
{
public List<TabInfo> GetTabs()
{
return new List<TabInfo>()
{
new TabInfo() { TabName="Tab1", MyUserControl = new UserControl1()},
new TabInfo() { TabName="Tab2", MyUserControl = new UserControl2()}
};
}
}
public class TabInfo
{
public string TabName { get; set; }
public UserControl MyUserControl { get; set; }
}
And now I have two usercontrols UserControl1 and UserControl2 each has a text box control. I would like to update the Text property of the textbox control in the UserControl1 whenever the Text property of the Textbox control in the UserControl2 is updated. To do this, I tried as below.
UserControl1
<UserControl x:Class="MoreOnBinding2.UserControl1"
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">
<Grid>
<TextBox Width="200" Height="20" Text="{Binding Path=UserControl1Text}"/>
</Grid>
UserControl1ViewModel
public class UserControl1VM : ViewModelBase
{
public UserControl1VM()
{
this.UserControl1Text = "10";
}
private string userControl1Text;
public string UserControl1Text
{
get { return userControl1Text; }
set
{
if (userControl1Text != value)
{
userControl1Text = value;
RaisePropertyChanged(() => UserControl1Text);
}
}
}
}
UserControl2
<UserControl x:Class="MoreOnBinding2.UserControl2"
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:MoreOnBinding2"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<TextBox Width="200" Height="20"
Text="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:UserControl1}},
UpdateSourceTrigger=PropertyChanged, Path=UserControl1Text}" />
</Grid>
But it is not working. It seems there is a problem in the RelativeSource tag in the UserControl2. Can someone help on this.
You could use a Dependency Property on your user controls this would give you a bind able property. see link
EDIT 1
Just had another look at your class setup. If you are following the MVVM approach I don't think you should have a reference to your user control in your view model
View Model should be more Like
public Class
{
ObservableCollection<TabInfo> _Tabs = new ObservableCollection<TabInfo>();
public ObservableCollection<TabInfo> Tabs
{
get{return _Tabs;}
set {_Tabs = value;}//Need to implement INotifyPropertyChanged [Link][2]
}
public TabInfo SelectedTab {get;set;}
}
public class TabInfo
{
public string TabName { get; set; }
public UserControlViewModel MyUserControlViewModel{ get; set; }
}
Then in your View
<Window.DataContext>
<vm:MainWindowModel />
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ListBox Grid.Column="0" ItemsSource="{Binding Tabs}" IsSynchronizedWithCurrentItem="True" SelectedValue="{Binding SelectedTab}">
<ListBox.ItemTemplate>
<DataTemplate>
<Label Content="{Binding Path=TabName}" Margin="10"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<ContentControl Grid.Column="1" Content="{Binding SelectedTab.MyUserControlViewModel"/>
</Grid>
This should give you a list of Tabs and then when one is selected it will set the content of the ContentControl to the MyUserControlViewModel.
You would then need to use some sort of template selector to control the ContentTemplate to load different UserControls into the ContentControl
I haven't tested the code works and you would need to implement the INotifyPropertyChanged on all the public properties so the bindings update as the properties value is changed.
Hope that helps a bit.

Categories