There is a form in which the label is located, as well as a user control wrapped in a button. I need to change the text in the TextBlock by clicking the button in the user control. I didn't use any MVVM frameworks.
Main XAML:
<Page.DataContext>
<local:MainViewModel />
</Page.DataContext>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="553*" />
<ColumnDefinition Width="197*" />
</Grid.ColumnDefinitions>
<local:ControlView Grid.Column="1" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" />
<TextBlock Grid.Column="0" Foreground="Black" FontSize="50" VerticalAlignment="Stretch"
HorizontalAlignment="Stretch" Text="{Binding UserText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</Grid>
User Conrol Xaml:
<Grid>
<Button Width="300" Height="100" FontSize="50" Command="{Binding}"
VerticalAlignment="Center" HorizontalAlignment="Center" Content="Print chart" />
</Grid>
Main VM:
public class MainViewModel : INotifyPropertyChanged
{
public MainViewModel()
{
SetText = new RelayCommand(SetUserText);
}
public ICommand SetText { get; set; }
public string UserText { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
private void SetUserText()
{
UserText = "Hello World!";
RaisePropertyChanged(nameof(UserText ));
}
protected void RaisePropertyChanged(string property)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
}
}
And I create an empty user control VN:
public class ControlViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string property)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
}
// And what should I do here?
}
So how to correctly implement commands from user control to main view, f.e SetText property?
Your main issue is there is the use of the ControlViewModel. UserControls such as ControlView should generally speaking not set their own DataContext to some specialized view model but rather inherit the DataContext from their parent element.
Assuming you let the control inherit the DataContext, you could add a dependency property to it and bind the command to this one:
<local:ControlView YourCommand="{Binding SetText}" />
ControlView.xaml:
<UserControl ... Name="uc">
<Button Command="{Binding YourCommand, ElementName=uc}" />
Related
I have a Main window named "wpfMenu" With a status bar which contains a text block and progress bar. The status bar needs to be updated from methods which are running on separate windows launched from the Main window (only one window open at any time).
Preferably I would like to pass the min, max, progress, text values to a class called "statusUpdate" to update the progress but i have no idea where to begin and any examples of updating progress bars I've come across are running on the same window.
Here is my code for the Status bar so far
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Custom="http://schemas.microsoft.com/winfx/2006/xaml/presentation/ribbon" x:Class="Mx.wpfMenu"
Title="Mx - Menu" Height="600" Width="1000" Background="#FFF0F0F0" Closed="wpfMenu_Closed">
<Grid>
<StatusBar x:Name="sbStatus" Height="26" Margin="0,0,0,0" VerticalAlignment="Bottom" HorizontalAlignment="Stretch">
<StatusBar.ItemsPanel>
<ItemsPanelTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="4*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
</Grid>
</ItemsPanelTemplate>
</StatusBar.ItemsPanel>
<StatusBarItem>
<TextBlock Name="sbMessage" Text="{Binding statusUpdate.Message}"/>
</StatusBarItem>
<StatusBarItem Grid.Column="1">
<ProgressBar Name="sbProgress" Width="130" Height="18" Minimum="0" Maximum="100" IsIndeterminate="False"/>
</StatusBarItem>
</StatusBar>
</Grid>
The code for my class is
public class statusUpdate : INotifyPropertyChanged
{
private string _message;
public event PropertyChangedEventHandler PropertyChanged;
public statusUpdate()
{
}
public statusUpdate (string value)
{
this._message = value;
}
public string Message
{
get { return _message; }
set
{
_message = value;
OnPropertyChanged("Message");
}
}
void OnPropertyChanged(string _message)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(_message));
}
}
}
There are several steps to this, but they're all well documented elsewhere. It might seem like a complex process, but it's something you'll do over and over in WPF.
You're right to store all the settings in a class. However this class needs to implement INotifyPropertyChanged and raise a PropertyChanged event in every property setter.
using System.ComponentModel;
public class StatusUpdate : INotifyPropertyChanged
{
private string message;
public event PropertyChangedEventHandler PropertyChanged;
public StatusUpdate()
{
}
public string Message
{
get { return this.message; }
set
{
this.message = value;
this.OnPropertyChanged("Message");
}
}
void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Then you can make it a public property of your code-behind class, and bind your progress bar properties to it.
public partial class MainWindow : Window
{
public MainWindow()
{
this.InitializeComponent();
}
public StatusUpdate Status { get; set; } = new StatusUpdate();
private void PlayCommand_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
}
public void PlayCommand_Executed(object sender, ExecutedRoutedEventArgs e)
{
this.Status.Message = "Play";
}
public void StopCommand_Executed(object sender, ExecutedRoutedEventArgs e)
{
this.Status.Message = "Stop";
}
}
Then you can pass a reference to the same class to the child forms, and when they set any of the properties, WPF will catch the event and update the GUI.
Let me know if you can't find an example for any of those steps.
Here's a version of your XAML with the binding and buttons I used for the above example:
<Window x:Class="WpfProgressBar.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:WpfProgressBar"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.CommandBindings>
<CommandBinding Command="Play" Executed="PlayCommand_Executed" CanExecute="PlayCommand_CanExecute" />
<CommandBinding Command="Stop" Executed="StopCommand_Executed" />
</Window.CommandBindings>
<Grid>
<StackPanel>
<Button Content="Play" Command="Play" />
<Button Content="Stop" Command="Stop" />
</StackPanel>
<StatusBar Height="26" Margin="0,0,0,0" VerticalAlignment="Bottom" HorizontalAlignment="Stretch">
<StatusBar.ItemsPanel>
<ItemsPanelTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="4*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
</Grid>
</ItemsPanelTemplate>
</StatusBar.ItemsPanel>
<StatusBarItem>
<TextBlock Text="{Binding Status.Message, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, NotifyOnSourceUpdated=True}"/>
</StatusBarItem>
<StatusBarItem Grid.Column="1">
<ProgressBar Width="130" Height="18" Minimum="0" Maximum="100" IsIndeterminate="False"/>
</StatusBarItem>
</StatusBar>
</Grid>
</Window>
NB, I wouldn't normally do command bindings like this, but didn't want to get into the complications of adding RelayCommand
Issue
I have a number of buttons in my WPF Window which when clicked need to change the view on the Window but keep the same ViewModel. Yesterday I tried using ControlTemplate for this but people mentioned I was better using a DataTemplate.
I need the binding to happen Via the ViewModel as well as I need to do some checks to see if the user can access the view.
Code
This is some of the code i started to write but I feel like its incorrect.
Here is the DataTemplate that I have defined in my view in Window.Resources:
<DataTemplate x:Key="panel1">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="7*"/>
<ColumnDefinition Width="110*"/>
<ColumnDefinition Width="190*"/>
<ColumnDefinition Width="110*"/>
<ColumnDefinition Width="202*"/>
<ColumnDefinition Width="109*"/>
<ColumnDefinition Width="7*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="74*"/>
<RowDefinition Height="50*"/>
<RowDefinition Height="12*"/>
<RowDefinition Height="39*"/>
<RowDefinition Height="11*"/>
<RowDefinition Height="38*"/>
<RowDefinition Height="5*"/>
</Grid.RowDefinitions>
<StackPanel Grid.Column="2" Grid.ColumnSpan="3" Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center">
<Label Content="Video Set:" Foreground="#e37e6e" Grid.Column="2" Grid.ColumnSpan="3" VerticalAlignment="Center" FontSize="22" HorizontalAlignment="Center"/>
<Image Source="{Binding VideoSet}" Height="25" Width="25" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</StackPanel>
<TextBlock Foreground="#e37e6e" FontSize="12" Text="You currently do not have a video set. Please click the button below to add a video. Please note you will not be able to create an On Demand presentation without a media file selected. " Grid.Row="1" Grid.Column="2" TextWrapping="WrapWithOverflow" TextAlignment="Center" Grid.ColumnSpan="3" />
<Button Style="{StaticResource loginButton}" Command="{Binding ScreenBack}" Foreground="White" Content="Add Video" Grid.Column="3" HorizontalAlignment="Stretch" Grid.Row="3" VerticalAlignment="Stretch" Grid.ColumnSpan="1"></Button>
</Grid>
</DataTemplate>
Then I tried to use a ContentPresenter and bind to the DataTemplate:
<ContentPresenter Content="{Binding}" Grid.Row="3" Grid.RowSpan="1" Grid.ColumnSpan="5"/>
Now I want to be able to bind different DataTemplates to the ContentPresenter Via the ViewModel, can anyone help me with this issue?
EDIT:
I can bind the ContentPresenter to the DataTemplate through the static resource like below:
<ContentPresenter ContentTemplate="{StaticResource panel1}" Content="{StaticResource panel1}" Grid.Row="3" Grid.RowSpan="1" Grid.ColumnSpan="5"/>
The DataTemplate like below:
<DataTemplate x:Key="panel1">
</DataTemplate>
But how can i change the ControlPresenter binding from the ViewModel?
EDIT:
Here is my code cycle:
So here are the two DataTemplates:
<DataTemplate DataType="{x:Type local:ViewModelA}">
<TextBlock Foreground="#e37e6e" FontSize="12" Text="You currently do not have a video set. Please click the button below to add a video. Please note you will not be able to create an On Demand presentation without a media file selected. " Grid.Row="1" Grid.Column="2" TextWrapping="WrapWithOverflow" TextAlignment="Center" Grid.ColumnSpan="3" />
</DataTemplate>
<DataTemplate DataType="{x:Type local:ViewModelB}">
<TextBlock Foreground="#e37e6e" FontSize="12" Text="NEWWWWWWWWWWYou" Grid.Row="1" Grid.Column="2" TextWrapping="WrapWithOverflow" TextAlignment="Center" Grid.ColumnSpan="3" />
</DataTemplate>
The my ContentControl:
<ContentControl Content="{Binding SelectedViewModel}" />
I defined my DataContext in the code behind:
WizardViewModel _wizardViewModel = new WizardViewModel();
this.DataContext = _wizardViewModel;
In the WizardViewModel i have:
namespace Podia2016.ViewModels
{
public class WizardViewModel : INotifyPropertyChanged
{
public object SelectedViewModel { get; set; }
ViewModelA s = new ViewModelA();
ViewModelB d = new ViewModelB();
public WizardViewModel()
{
SelectedViewModel = s;
OnPropertyChanged("SelectedViewModel");
}
//BC - BINDS TO CHANGE LECTURE.
public ICommand Next
{
get { return new DelegateCommand<object>(Next_Click); }
}
private void Next_Click(object obj)
{
SelectedViewModel = d;
OnPropertyChanged("SelectedViewModel");
}
}
public class ViewModelA : INotifyPropertyChanged
{
//BC - DEFAULT ONPROPERTYCHANGED EVENT.
public event PropertyChangedEventHandler PropertyChanged;
public ViewModelA()
{
}
/// <summary>
/// This is the standard OnPropertyChanged Event Method
/// </summary>
/// <param name="name"></param>
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
public class ViewModelB : INotifyPropertyChanged
{
//BC - DEFAULT ONPROPERTYCHANGED EVENT.
public event PropertyChangedEventHandler PropertyChanged;
public ViewModelB()
{
}
/// <summary>
/// This is the standard OnPropertyChanged Event Method
/// </summary>
/// <param name="name"></param>
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
}
Data templating is much simpler to use (compared to your attempt):
create sub viewmodels, one for each sub view you want to have
// could have base class, may have to implement INotifyPropertyChanged, etc.
public class ViewModelA { }
public class ViewModelB
{
public string SomeProperty { get; }
}
...
public object SelectedViewModel { get; set; }
define data templates
<SomeContainer.Resources>
<!-- using user control -->
<DataTemplate DataType="{x:Type local:ViewModelA}">
<local:UserControlA />
</DataTemplate>
<!-- or like this -->
<DataTemplate DataType="{x:Type local:ViewModelB}">
<Grid>
<TextBlock Text="{Binding SomeProperty}" />
<Button .../>
...
</Grid>
</DataTemplate>
Bind content control to a property which select sub viewmodel
<ContentControl Content="{Binding SelectedViewModel}" />
control what to display by changing selected sub viewmodel:
var a = new ViewModelA();
var b = new ViewModelB() { SomeProperty = "Test" };
// display a - will display UserControlA content in ContentControl
SelectedViewModel = a;
OnPropertyChanged(nameof(SelectedViewModel));
// display b - will display text and button in ContentControl
SelectedViewModel = b;
OnPropertyChanged(nameof(SelectedViewModel));
Store DataTemplate as property of your ViewModel. Access the DataTemplate from ResourceDictionary to store in your property.
Bind <ContentPresenter Content="{Binding}" ContentTemplate="{Binding template1}" .../>
How to access ResourceDictionary from code :
If you have in your WPF project an ResourceDictionary that you use to define resources you can create an instance of it from code like this:
ResourceDictionary res = Application.LoadComponent(
new Uri("/WpfApplication1;component/MyDataTemplateCollection.xaml",
UriKind.RelativeOrAbsolute)) as ResourceDictionary;
Where WpfApplication1 is name of your assembly and MyDataTemplateCollection.xaml is name of your ResourceDictionary.
Another way is to use the code-behind for the resource dictionary.
Add x:Class to your ResourceDictionary:
Add class MyDataTemplateCollection.xaml.cs as your code-behind for the ResourceDictionary.
The code behind class looks like so:
partial class MyDataTemplateCollection: ResourceDictionary
{
public MyDataTemplateCollection()
{
InitializeComponent();
}
}
Usage :
ResourceDictionary res = new MyDataTemplateCollection();
So, I tested many answers I found on different topics, but still my WPF app does not update binded data. When I set all Properties before Initializing MainWindow Data are displayed correctly, but I need to select directory, date, etc. before loading the data. Tried to change DataContext in code behind, but IT doesn't work. All the classes used as VieModels have implemented INotifyPropertyChanged interface (but the PropertyChanged values is always null). I'm out of ideas now...
This is XAML code:
<Window x:Class="WpfDataBinding.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:WpfDataBinding"
mc:Ignorable="d"
Title="MainWindow" Height="600" Width="800" Name="Logs">
<Window.DataContext>
<local:CustomDataContexts />
</Window.DataContext>
<Grid Name="Logi">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<local:CustomButton Grid.Column="0" Grid.Row="1" Margin="5" Height="35" Width="100" x:Name="Choose" Text="Wybierz:" ImageSource="Resources/choose.png" Click="CustomButton_Click" />
<local:CustomButton Grid.Column="0" Grid.Row="2" Margin="5" Height="35" Width="100" x:Name="Load" Text="ZaĆaduj:" ImageSource="Resources/load.png" Click="CustomButton_Click" />
<local:CustomButton Grid.Column="0" Grid.Row="3" Margin="5" Height="35" Width="100" x:Name="Search" Text="Szukaj:" ImageSource="Resources/search.png" Click="CustomButton_Click" />
<local:CustomButton Grid.Column="3" Grid.Row="2" Margin="5" Height="80" Width="100" x:Name="Next" Grid.RowSpan="2" Text="Dalej:" ImageSource="Resources/next.png" Click="CustomButton_Click" />
<TabControl DataContext="{Binding TextViewModel}" x:Name="tabControl" Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" ItemsSource="{Binding TxtView.Tabs, ElementName=Logs, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}">
<TabControl.ItemTemplate>
<!-- this is the header template-->
<DataTemplate>
<TextBlock Text="{Binding Header}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate >
<!-- this is the body of the TabItem template-->
<DataTemplate>
<ListView ItemsSource="{Binding EntryViewModels}">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Entry.Tag}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
<TabControl DataContext="{Binding SingleNode}" x:Name="tabControl2" Grid.Row="0" Grid.Column="2" Grid.ColumnSpan="2" ItemsSource="{Binding Tabs}">
<TabControl.ItemTemplate>
<!-- this is the header template-->
<DataTemplate>
<TextBlock Text="{Binding Header}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<ListView ItemsSource="{Binding Content}" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>
</Window>
Code-behind looks like this:
public CustomDataContexts DataContexts { get; set; }
public string Path { get; set; }
public Files Files { get; set; }
public MainWindow()
{
/*Path = #"C:\Users\Slawek\Desktop\Logs\logi";
Files = new Files(Path);
Files.NarrowFiles(false, DateTime.MinValue);
var entry = new EntryCollection(Files.SelectedFiles[1], Files.SelectedFiles, null);
TxtView = new TxtViewModel(new List<TxtTabItem>(new[] { new TxtTabItem(entry) }));*/
InitializeComponent();
}
private void CustomButton_Click(object sender, RoutedEventArgs e)
{
var fe = (FrameworkElement) sender;
switch (fe.Name)
{
case "Choose":
var g = new FolderBrowserDialog();
if (g.ShowDialog() == System.Windows.Forms.DialogResult.OK)
Path = g.SelectedPath;
break;
case "Load":
DataContexts = new CustomDataContexts();
Files = new Files(Path);
Files.NarrowFiles(false, DateTime.MinValue);
var entry = new EntryCollection(Files.SelectedFiles[1], Files.SelectedFiles, null);
DataContexts.TextViewModel = new TxtViewModel(new List<TxtTabItem>(new[] { new TxtTabItem(entry) }));
break;
case "Search":
break;
case "Next":
break;
}
}
CustomDataContexts class:
public class CustomDataContexts : INotifyPropertyChanged
{
private TxtViewModel textViewModel;
public XmlViewModel SingleNode { get; set; }
public TxtViewModel TextViewModel
{
get { return textViewModel; }
set { OnPropertyChanged("TextViewModel"); }
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
EntryViewModel:
public class EntryViewModel : INotifyPropertyChanged
{
private SingleEntry entry;
public SingleEntry Entry
{
get { return entry; }
set
{
entry = value;
OnPropertyChanged("Entry");
}
}
public EntryViewModel(SingleEntry entry)
{
Entry = entry;
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
TxtViewModel:
public class TxtViewModel :INotifyPropertyChanged
{
private ObservableCollection <TxtTabItem> tabs;
public ObservableCollection<TxtTabItem> Tabs
{
get { return tabs; }
set
{
tabs = value;
OnPropertyChanged("Tabs");
}
}
public TxtViewModel(List<TxtTabItem> items)
{
Tabs = new ObservableCollection <TxtTabItem>();
foreach (var txtTabItem in items)
{
Tabs.Add(txtTabItem);
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
And finally TxtTabItem class:
public class TxtTabItem : INotifyPropertyChanged
{
public string Header { get; set; }
public ObservableCollection<EntryViewModel> EntryViewModels { get; set; }
public TxtTabItem(EntryCollection collection)
{
Header = collection.Date.ToShortDateString();
EntryViewModels = new ObservableCollection <EntryViewModel>();
foreach (var entry in collection.Entries)
{
EntryViewModels.Add(new EntryViewModel(entry));
}
OnPropertyChanged("EntryViewModels");
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
I will be very grateful for any suggestions of how to make this code work. I'm pretty new to WPF and still don't know it well enough.
The framework is there, but generally to intialize controls to changes one binds to the ItemsSource property instead of directly to the DataContext.
In general the DataContext is something that provides information about the current item, but when it is null the parent's data context is used up until the page's data context.
The reason for that is a data context will hold a large class of properties and the binding will look (reflect/reflection) at the DataContext for that named property. So by using an ItemsSource one can bind to a specific set of items, while still having a DataContext full of information to allow other items on the control to be bound to other specific properties.
Under MVVM where the VM or view model is that class of properties as mentioned, set the page's data context to that VM and then bind to individual properties on the different controls you have from the page's datacontext.
so
<TabControl DataContext="{Binding TextViewModel}" x:Name="tabControl"
becomes
<TabControl ItemsSource="{Binding TextViewModel}" x:Name="tabControl"
once you set the page's data context to TxtViewModel.
I provide a binding/VM example on my blog article Xaml: ViewModel Main Page Instantiation and Loading Strategy for Easier Binding.
I got here a template combo box
<ComboBox x:Name="TryCombo" >
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="200"/>
<ColumnDefinition Width="75"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Path=Id}" Margin="4,0" Visibility="Collapsed" Grid.Column="0"/>
<TextBlock Text="{Binding Path=Name}" Margin="4,0" Grid.Column="1"/>
<Button x:Name="AddButton" Content="Add" Grid.Column="2"/>
</Grid>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
then the ItemsSource:
public void BindComboboxes()
{
itemMgr.Parameters = RetrieveFilter("");
itemMgr.EntityList = itemMgr.RetrieveMany(itemMgr.Parameters);
TryCombo.ItemsSource = itemMgr.EntityList; //collection;
}
the combo box will load this:
My problem is getting the selected item that I had clicked with the AddButton, I want to get the value of text block that was bound to Path=Id, but how?
Should I put x:Name for each of the TextBlocks?
Bind your Button to a ICommand-Property in your ViewModel and pass the selected Item as CommandParameter
I've made a small Demo-Application for you:
The MainWindow.xaml looks like:
<Window x:Class="ComboBoxDemo.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"
WindowStartupLocation="CenterScreen"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Button Content="Add Item to ComboBox" Margin="5" Command="{Binding AddItemCommand}"/>
<ComboBox Grid.Row="1" x:Name="TryCombo" ItemsSource="{Binding Parameters, UpdateSourceTrigger=PropertyChanged}" Margin="5">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="50"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Path=Id}" Margin="4,2" VerticalAlignment="Center" Grid.Column="0"/>
<TextBlock Text="{Binding Path=Name}" Margin="4,2" Grid.Column="1" VerticalAlignment="Center"/>
<Button x:Name="AddButton" Content="Add" Grid.Column="2" VerticalAlignment="Center" Margin="4,2"
Command="{Binding DataContext.ComboBoxItemAddCommand, RelativeSource={RelativeSource FindAncestor, AncestorType=Window}}"
CommandParameter="{Binding}"/>
</Grid>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</Grid>
</Window>
Important is the part in the Window-Declaration of: DataContext="{Binding RelativeSource={RelativeSource Self}}" With this you tell the Window that it's DataContext is in the CodeBehind-File. You also can use another File as your DataContext. Than you have to write:
<Window.DataContext>
<loc:MyClassName/>
</Window.DataContext>
This only works if you add xmlns:loc="clr-namespace:YOURPROJECTNAMESPACE" to the Window-Declaration. In the case of my Demo YOURPROJECTNAMESPACE would be ComboBoxDemo.
I've also created a class for the Parameter which is very simple and looks like:
public class Parameter
{
public string Id { get; set; }
public string Name { get; set; }
}
The CodeBehind of the Window (remember: This is the DataContext of the MainWindow.xaml) looks like:
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Input;
namespace ComboBoxDemo
{
public partial class MainWindow : Window, INotifyPropertyChanged
{
private ICommand addItemCommand;
private ICommand comboBoxItemAddCommand;
private ObservableCollection<Parameter> parameters;
public MainWindow()
{
InitializeComponent();
Parameters = new ObservableCollection<Parameter>();
AddItemCommand = new RelayCommand(AddItem);
ComboBoxItemAddCommand = new RelayCommand(ComboBoxItemAdd);
}
private void ComboBoxItemAdd(object parameter)
{
Parameter para = parameter as Parameter;
if (para != null)
{
// Now you can use your Parameter
}
}
public ObservableCollection<Parameter> Parameters
{
get { return parameters; }
set
{
parameters = value;
OnPropertyChanged();
}
}
public ICommand AddItemCommand
{
get { return addItemCommand; }
set
{
addItemCommand = value;
OnPropertyChanged();
}
}
public ICommand ComboBoxItemAddCommand
{
get { return comboBoxItemAddCommand; }
set
{
comboBoxItemAddCommand = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void AddItem(object parameter)
{
Parameters.Add(new Parameter
{
Id = Guid.NewGuid().ToString(),
Name = "Any Name"
});
}
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
And I also created a very useful Helper-Class which you will likely always need if you are using command-binding. This is the class RelayCommand. This class has a constructer which wants an object of type Action<object>. This action later contains what will be executed when you click the button. The seconde optional parameter I will not explain now. The RelayCommand looks like:
using System;
using System.Windows.Input;
namespace ComboBoxDemo
{
public class RelayCommand : ICommand
{
private readonly Action<object> execute;
private readonly Predicate<object> canExecute;
public RelayCommand(Action<object> execute, Predicate<object> canExecute = null )
{
if (execute == null)
throw new ArgumentNullException("execute");
this.execute = execute;
this.canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
if (canExecute == null)
return true;
return canExecute(parameter);
}
public void Execute(object parameter)
{
execute(parameter);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
}
}
So. Now I will explain you the process: Therefor I use the following abbreviations
V = MainWindow.xaml
VM = MainWindow.xaml.cs
The ComboBox in the V has your given Itemtemplate. In the definition of the ComboBox I've added ItemsSource="{Binding Parameters, UpdateSourceTrigger=PropertyChanged}". This tells the ComboBox that it will get it's children from this collection which is located in the VM.
The collection in the VM is an ObservableCollection<Parameter>. The advantage of this type of Collection is, that it implements the ICollectionChanged-Interface and so your V will get updated if there are items added or removed to this collection.
The Button in the V just adds a dummy-Parameter to the ObservableCollection<Parameter>.
With Command="{Binding AddItemCommand}" I tell the Button that it's command-property is bound to the AddItemCommand in the DataContext. In the constructor of the DataContext (MainWindow.xaml.cs) I'm creating this Command and provide the AddItem-Method which will be called if the command is executed.
The Binding of the Button in the DataTemplate must provide a RelativeSource because inside the Template the DataContext is another one. With the RelativeSource I can tell the Button that its Command-Property is bound to a Command which is located in the DataContext of the Window.
I hope this helps you.
If you want to go deeper into the MVVM-Pattern take a look at this Link
One quick way to do it is to simply bind the Tag property of the button to the same property as the TextBlock.
<Button Tag="{Binding Path=Id}" />
Then in the event handler for the button click event you can cast the sender to Button and get the ID from the Tag property.
int id = Convert.ToInt32((sender as Button).Tag);
I'm trying to make an activity designer library. I have two sources; one of them is CodeActivity in C# code and the other one is Activity designer in XAML. In CodeActivity, I have a public property Name. In XAML, I want to view and change it's value through binding. My XAML design is like this
I declared Name property like this:
private string _name;
public string Name {
get { return _name; }
set
{
_name = value;
NotifyPropertyChanged("Name");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this,
new PropertyChangedEventArgs(propertyName));
}
}
And my XAML is like this:
...
<DataTemplate x:Key="Expanded">
<StackPanel>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="25"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="40"/>
<ColumnDefinition Width="130"/>
</Grid.ColumnDefinitions>
<TextBox x:Name="txtName" Grid.Column="1" Grid.Row="0" Text="{Binding Name, Mode=TwoWay}"/>
<TextBlock Grid.Column="0" Grid.Row="0" Text="Name :" HorizontalAlignment="Right"/>
</Grid>
<sap:WorkflowItemPresenter Item="{Binding Path=ModelItem.Body, Mode=TwoWay}"
HintText="Please drop an activity here" />
</StackPanel>
</DataTemplate>
I've tried a lot of ways, but I couldn't do it.
How can I show the Name property from CodeActivity in XAML?
I got it.
When we want to bind a variable from CodeActivity Side to XAML, we do like this :
...
xmlns:s="clr-namespace:System;assembly=mscorlib"
<sap:ActivityDesigner.Resources>
<sapc:ArgumentToExpressionConverter x:Key="ArgumentToExpressionConverter" />
...
<sapv:ExpressionTextBox HintText="Enter custom text here ..." Expression="{Binding Path=ModelItem.Text, Mode=TwoWay, Converter={StaticResource ArgumentToExpressionConverter}, ConverterParameter=In}" ExpressionType="s:String" OwnerActivity="{Binding Path=ModelItem}" MaxLines="1"/>
...