I created a WPF sample (using caliburn micro with MVVM pattern, no code-behind) with a view model and their related views:
ShellView.xaml and ShellViewModel.cs
The ShellView contains:
A ComobBox, which contains a list of string, if this combox selection is changed, it will raise comboBox1_SelectionChanged() in ShellViewModel.
A Button, if click this button, it will raise Button1_Click() to delete the first item of list in ShellViewModel.
My questions:
If I want to click the button without trigger comboBox1_SelectionChanged in view model, how to do that?
If it implemented in code-behind, I can do like this:
public void Button1_Click(object sender, EventArgs e)
{
comboBox1.SelectionChanged -= comboBox1_SelectionChanged;
MyCollection.RemoveAt(0);
comboBox1.SelectionChanged += comboBox1_SelectionChanged;
}
I have no idea how to achieve this in view model. The following is the code:
ShellView.xaml
<UserControl x:Class="WpfApp.Views.ShellView"
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:WpfApp.Views"
xmlns:cal="http://caliburnmicro.com"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height=" auto"/>
<RowDefinition Height=" auto"/>
</Grid.RowDefinitions>
<ComboBox Name="comboBox1" Grid.Row="0" ItemsSource="{Binding MyCollection}" SelectedValue="{Binding SelectMyListValue}"
cal:Message.Attach="[Event SelectionChanged]=[Action comboBox1_SelectionChanged($source,$eventArgs)]" />
<Button Name="Button1" Grid.Row="1" Content="Delete"
cal:Message.Attach="[Event Click]=[Action Button1_Click($source,$eventArgs)]" />
</Grid>
</UserControl>
ShellViewModel.cs
using Caliburn.Micro;
using System;
using System.Windows.Controls;
namespace WpfApp.ViewModels
{
public class ShellViewModel : Conductor<object>.Collection.OneActive
{
private BindableCollection<string> _myCollection = new BindableCollection<string>() { "item1", "item2"};
public BindableCollection<string> MyCollection
{
get => _myCollection;
set
{
_myCollection = value;
NotifyOfPropertyChange(() => MyCollection);
}
}
private string _selectMyListValue = "item1";
public string SelectMyListValue
{
get => _selectMyListValue;
set
{
_selectMyListValue = value;
NotifyOfPropertyChange(nameof(SelectMyListValue));
}
}
public void comboBox1_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
// Do something...
}
public void Button1_Click(object sender, EventArgs e)
{
MyCollection.RemoveAt(0);
}
}
}
Thank you in advance.
Your requirement can't be fully met, as when you remove the selected item from the collection a change of SelectedValue (to null) is inevitable.
Furthermore: You don't need to bind to the SelectionChanged event. You already have a binding to SelectedValue, so the setter of the bound property is called when the selection changes. This doesn't happen, when you remove a value from the collection that is not currently selected.
I would also recommend not to subscribe to the Clicked event of the button, but to bind an ICommand (added to your viewmodel) to the Command property of the button. An easy to use implementation would be the RelayCommand from the Windows Community Toolkit. You can read about it here: https://learn.microsoft.com/en-us/windows/communitytoolkit/mvvm/relaycommand. It also isn't difficult to implemnt a version on your own, if you don't want to use the whole toolkit.
Code sample:
public class RelayCommand : ICommand
{
private readonly Action<object?> execute;
private readonly Func<object?, bool> canExecute;
public RelayCommand(
Action<object?> execute,
Func<object?, bool>? canExecute = null)
{
this.execute = execute;
this.canExecute = canExecute ?? (_ => true);
}
public bool CanExecute(object? parameter) => this.canExecute(parameter);
public void Execute(object? parameter)
{
this.execute(parameter);
}
public event EventHandler? CanExecuteChanged;
}
// on your viewmodel add...
public ICommand RemoveFirstItemCommand { get; set; }
private void RemoveFirstItem(object? param)
{
if (this.Items.Count > 0)
{
this.Items.RemoveAt(0);
}
}
// ...and in the constructor init the command
this.RemoveFirstItemCommand = new RelayCommand(this.RemoveFirstItem);
I got a solution which achieved the goal, but I'm not sure if it's the right way.
There is a "Microsoft.Xaml.Behaviors" which provided "Interaction.Triggers" that contains "ComparisonCondition". I can use it to bind a value to determine the EventCommand is raised or not.
I updated the code as following:
ShellViewModel.cs
using Caliburn.Micro;
using System;
using System.Windows.Controls;
using WpfApp.Commands;
namespace WpfApp.ViewModels
{
public class ShellViewModel : Conductor<object>.Collection.OneActive
{
private bool _IsEnableSelectionChangedCommand = true;
public bool IsEnableSelectionChangedCommand
{
get => _IsEnableSelectionChangedCommand;
set
{
_IsEnableSelectionChangedCommand = value;
NotifyOfPropertyChange(() => IsEnableSelectionChangedCommand);
}
}
private BindableCollection<string> _myCollection = new BindableCollection<string>() { "item1", "item2"};
public BindableCollection<string> MyCollection
{
get => _myCollection;
set
{
_myCollection = value;
NotifyOfPropertyChange(() => MyCollection);
}
}
private string _selectMyListValue = "item1";
public DelegateCommand<object> DoSelectionChangedCommand { get; }
public ShellViewModel()
{
DoSelectionChangedCommand = new DelegateCommand<object>(comboBox1_SelectionChanged, CanExecute);
}
private bool CanExecute(object param)
{
return true;
}
private void comboBox1_SelectionChanged(object param)
{
SelectionChangedEventArgs e = param as SelectionChangedEventArgs;
ComboBox item = e.Source as ComboBox;
// Do something...
}
public string SelectMyListValue
{
get => _selectMyListValue;
set
{
_selectMyListValue = value;
NotifyOfPropertyChange(nameof(SelectMyListValue));
}
}
public void Button1_Click(object sender, EventArgs e)
{
IsEnableSelectionChangedCommand = false;
MyCollection.RemoveAt(0);
IsEnableSelectionChangedCommand = true;
}
}
}
ShellView.xaml
<UserControl x:Class="WpfApp.Views.ShellView"
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:cal="http://caliburnmicro.com"
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
xmlns:cmd="clr-namespace:WpfApp.Commands"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height=" auto"/>
<RowDefinition Height=" auto"/>
</Grid.RowDefinitions>
<ComboBox Name="comboBox1" Grid.Row="0" ItemsSource="{Binding MyCollection}" SelectedValue="{Binding SelectMyListValue}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<cmd:EventCommand Command="{Binding DoSelectionChangedCommand}" />
<i:Interaction.Behaviors>
<i:ConditionBehavior>
<i:ConditionalExpression>
<i:ComparisonCondition LeftOperand= "{Binding IsEnableSelectionChangedCommand}" Operator="Equal" RightOperand="True"/>
</i:ConditionalExpression>
</i:ConditionBehavior>
</i:Interaction.Behaviors>
</i:EventTrigger>
</i:Interaction.Triggers>
</ComboBox>
<Button Name="Button1" Grid.Row="1" Content="Delete"
cal:Message.Attach="[Event Click]=[Action Button1_Click($source,$eventArgs)]" />
</Grid>
</UserControl>
Related
How to store the content of TextBox in Model layer to be in line with MVVM?
I've made simple demo application to practice MVVM. It consists of main TextBox and 2 additional TextBoxes just for test if the app works properly.
In ViewModel I have TextContent class which implements INotifyPropertyChanged and it has Text property and the Text of MainTextBox is bindded to this and it works correctly.
In Model I have TextStore property which I try to update in the setter of Text property from ViewModel.TextContent, using simple method ModelUpdate().
And this model updating doesn't work.
Could you tell me ho can I transfer the content of TextBox which is stored in ViewModel property to the Model layer? And being in line in MVVM pattern?
Here the code:
View: (Here, the third TextBox is bindded to the model - I know, this is not compatible with MVVM idea but this is just for check the value of TextStore property from Model layer)
<Window x:Class="MVVM_TBDB_2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MVVM_TBDB_2"
xmlns:vm="clr-namespace:MVVM_TBDB_2.ViewModel"
xmlns:m="clr-namespace:MVVM_TBDB_2.Model"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<m:TextContent x:Key="ModelTextContent" />
</Window.Resources>
<Window.DataContext>
<vm:TextContent />
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="8*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBox Name="MainTB" Grid.Row="0" Margin="10" AcceptsReturn="True"
Text="{Binding Text, Mode=OneWayToSource, UpdateSourceTrigger=PropertyChanged}"/>
<Button Name="SaveButton" Content="Save" Grid.Row="1" Margin="10,2" Padding="20,0" HorizontalAlignment="Left" />
<TextBox Name="ControlTB" Grid.Row="1" Margin="30,2,2,2" Width="100" Text="{Binding Text, Mode=OneWay}" />
<TextBox Name="ControlTB2" Grid.Row="1" Margin="300,2,2,2" Width="100" DataContext="{StaticResource ModelTextContent}"
Text="{Binding TextStock, Mode=TwoWay}" />
</Grid>
</Window>
ViewModel:
class TextContent : INotifyPropertyChanged
{
private Model.TextContent model;
public TextContent()
{
model = new Model.TextContent();
}
private string _Text;
public string Text
{
get { return _Text; }
set
{
_Text = value;
OnPropertyChanged("Text");
ModelUpdate(_Text);
}
}
private void OnPropertyChanged(string parameter)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(parameter));
}
public event PropertyChangedEventHandler PropertyChanged;
private void ModelUpdate(string textToUpdate)
{
model.TextStock = textToUpdate;
}
}
Model:
class TextContent
{
private string _TextStock;
public string TextStock
{
get { return _TextStock; }
set { _TextStock = value; }
}
}
See Here I have implemented your requirement.
Attach the data context from code behind.
Implement the INotifyPropertyChanged interface in Model.
Make the TextStock property as binded property.
MainWindow.cs
public TextContent _model { get; set; }
public TextContentViewModel _viewModel { get; set; }
public MainWindow()
{
InitializeComponent();
_viewModel = new TextContentViewModel();
_model = new TextContent();
this.DataContext = _viewModel;
ControlTB2.DataContext = _model;
}
Your ViewModel Class
private TextContent model;
public TextContentViewModel()
{
}
private string _Text;
public string Text
{
get { return _Text; }
set
{
_Text = value;
OnPropertyChanged("Text");
if (model != null)
{
ModelUpdate(_Text);
}
else
{
model = ((Application.Current.MainWindow as MainWindow).ControlTB2).DataContext as TextContent;
}
}
}
private void OnPropertyChanged(string parameter)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(parameter));
}
public event PropertyChangedEventHandler PropertyChanged;
private void ModelUpdate(string textToUpdate)
{
model.TextStock = textToUpdate;
}
}
Model Class
private string _TextStock;
public string TextStock
{
get { return _TextStock; }
set { _TextStock = value; OnPropertyChanged("TextStock"); }
}
private void OnPropertyChanged(string parameter)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(parameter));
}
public event PropertyChangedEventHandler PropertyChanged;
Note: I have renamed the class names as per my convenience.
I'm not sure if this is possible but I'm looking for a way to bind a button to a generic class that contain all the properties i will need to use. Every button needs a relay command so that would be included but all of our buttons will need to bind visibility and being enabled. Instead of having this group of properties and relay command for every button we will use within the given windows view model I was wondering if there was a way to have the button bind to a class then in our view model we reference a new instance of that class for each button needed and then be just be able to set the properties on that class to the values we need. I hope this makes sense.
There's probably a bunch of different ways to do something like this. I don't know if I'd choose to have a class instance for each button. But here's a rough/quick/dodgy example of a solution.
The main model for the form is providing the button models by way of a list. The individual button models then handle the button bindings.
EDIT: Extended the code a bit. Now includes command bindings. Also shows use of ItemsControl as suggested by #Xavier. Hope it helps.
MainWindow.xaml:
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Title="MainWindow" Height="300" Width="400">
<StackPanel>
<!-- Known buttons -->
<StackPanel Margin="20">
<Button DataContext="{Binding ButtonModels[0], Mode=OneTime}" Content="{Binding LabelText}" Background="{Binding Colour}" Command="{Binding Command}" CommandParameter="{Binding CommandParameter}" />
<Button DataContext="{Binding ButtonModels[1], Mode=OneTime}" Content="{Binding LabelText}" Background="{Binding Colour}" Command="{Binding Command}" CommandParameter="{Binding CommandParameter}" />
<Button DataContext="{Binding ButtonModels[2], Mode=OneTime}" Content="{Binding LabelText}" Background="{Binding Colour}" Command="{Binding Command}" CommandParameter="{Binding CommandParameter}" />
</StackPanel>
<!-- Dynamic buttons -->
<StackPanel Margin="20">
<ItemsControl ItemsSource="{Binding ButtonModels}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding LabelText}" Background="{Binding Colour}" Command="{Binding Command}" CommandParameter="{Binding CommandParameter}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</StackPanel>
</Window>
MainWindow.xaml.cs:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
namespace WpfApp1
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new Model();
}
}
public class Model
{
private Random rnd = new Random();
public List<ButtonModel> ButtonModels { get; private set; }
public Model()
{
this.ButtonModels = new List<ButtonModel>();
for (int i = 0; i < 5; i++)
{
this.ButtonModels.Add(new ButtonModel
{
LabelText = "Button " + (i + 1),
Command = new RelayCommand((index) => { this.ChangeColour((int)index); }),
CommandParameter = i
});
}
}
private void ChangeColour(int index)
{
this.ButtonModels[index].Colour = new SolidColorBrush(Color.FromRgb((byte)rnd.Next(50, 256), (byte)rnd.Next(50, 256), (byte)rnd.Next(50, 256)));
}
}
public class ButtonModel : ObservableObject
{
private string _LabelText;
public string LabelText { get => _LabelText; set => this.SetProperty(ref _LabelText, value); }
private Brush _Colour = new SolidColorBrush(Color.FromRgb(205, 205, 205));
public Brush Colour { get => _Colour; set => this.SetProperty(ref _Colour, value); }
private RelayCommand _Command;
public RelayCommand Command { get => _Command; set => this.SetProperty(ref _Command, value); }
private int _CommandParameter;
public int CommandParameter { get => _CommandParameter; set => this.SetProperty(ref _CommandParameter, value); }
}
public class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected bool SetProperty<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
{
if (field == null && value == null)
{
return false;
}
if (field == null || !field.Equals(value))
{
field = value;
this.RaisePropertyChangedEvent(propertyName);
return true;
}
return false;
}
protected void RaisePropertyChangedEvent(string propertyName)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class RelayCommand : ICommand
{
private Action<object> execute;
private Predicate<object> canExecute;
public event EventHandler CanExecuteChanged;
public RelayCommand(Action<object> action, Predicate<object> canExecute = null)
{
this.execute = action;
this.canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return this.canExecute == null || this.canExecute(parameter);
}
public void Execute(object parameter)
{
this.execute(parameter);
}
}
}
I have a window with a listbox containing several items. There is a itemtemplate which defines they should be represented by a checkbox:
<Window x:Class="WpfApplication1.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:WpfApplication1"
xmlns:converters="clr-namespace:WpfApplication1.Converters"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid MouseLeftButtonDown="Grid_MouseLeftButtonDown">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ListBox ItemsSource="{Binding Customers}" >
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding IsChecked}" Content="{Binding Path=Item.Name}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button Grid.Row="1"
HorizontalAlignment="Right"
Margin="5"
Content="Test"
IsEnabled="{Binding Customers, Converter={converters:CustomersToBoolConverter}}"
/>
</Grid>
</Window>
Next, I declare the binded 'Customers' collection and its items in the code behind like so:
public partial class MainWindow : Window, INotifyPropertyChanged
{
public ObservableCollection<CheckedListItem<Customer>> Customers { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
public MainWindow()
{
InitializeComponent();
Customers = new ObservableCollection<CheckedListItem<Customer>>();
Customers.Add(new CheckedListItem<Customer>(new Customer() { Name = "Kelly Smith" }));
Customers.Add(new CheckedListItem<Customer>(new Customer() { Name = "Joe Brown" }));
Customers.Add(new CheckedListItem<Customer>(new Customer() { Name = "Herb Dean" }));
Customers.Add(new CheckedListItem<Customer>(new Customer() { Name = "John Paul" }));
DataContext = this;
}
void Grid_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("Customers"));
}
}
Here is the Customer class definition:
public class Customer
{
public string Name { get; set; }
}
Here is the definition of the CheckedListItem class:
public class CheckedListItem<T> : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private bool isChecked;
private T item;
public CheckedListItem()
{ }
public CheckedListItem(T item, bool isChecked = false)
{
this.item = item;
this.isChecked = isChecked;
}
public T Item
{
get { return item; }
set
{
item = value;
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("Item"));
}
}
public bool IsChecked
{
get { return isChecked; }
set
{
isChecked = value;
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("IsChecked"));
}
}
This latter implements my INPC logic.
I bind the status of my button to the Customers list and the output of a converter which returns true if any of the items is checked.
it works well when loading the window, however, if I check any item of the list, nothing happens. Moreover, if I implement the INPC logic in the class MainWindow and explicitly call
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("Customers"));
Then, the notification is sent to my window and the status of the button is refreshed just fine.
My question is how can I send this notification from within the CheckedListItem, is it possible?
Code source : http://www.jarloo.com/how-to-create-a-checkedlistbox-in-wpf/
You can bind to CollectionChanged event of ObservableCollection which Occurs when an item is added, removed, changed, moved, or the entire list is refreshed.
You can put the event subscription code in MainWindow Constructor
Customers.CollectionChanged += Customers_CollectionChanged;
And handler in code behind :
void Customers_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
/// Your code
}
I can bind a combobox in the codebehind like this:
private void comboBox1_Loaded(object sender, RoutedEventArgs e)
{
var combo = sender as ComboBox;
App.SchedulerVM = new ScheduleViewModel();
combo.DataContext = App.SchedulerVM;
combo.ItemsSource = App.SchedulerVM.Frequency;
}
This works - my combobox has the items from the Frequency List in the SchedulerVM object.
However, I don't want to do any of this in the codebehind. But the ways I've done this in WP7 before aren't working here. If I comment out the last line in the Loaded method above and try to set the ItemsSource in XAML, it doesn't work - nothing shows up:
<ComboBox Name="comboBox1" Loaded ="comboBox1_Loaded" ItemsSource="{Binding
Frequency}" />
This doesn't work either:
<ComboBox Name="comboBox1" Loaded ="comboBox1_Loaded" ItemsSource="{Binding
App.SchedulerVM.Frequency}" />
Nor this:
<ComboBox Name="comboBox1" Loaded ="comboBox1_Loaded" ItemsSource="{Binding
SchedulerVM.Frequency}" />
Ideally, the DataContext wouldn't have to be explicitly set in the codebehind for this control either, it would be inherited from the LayoutRoot, where I've set it in the codebehind. But that's step 2 of my troubleshooting here.
What am I doing wrong? '
Thanks!
Edit
The ScheduleViewModel looks like this:
namespace SchedulerUI.ViewModels
{
public class ScheduleViewModel : INotifyPropertyChanged
{
//private properties
private Schedule _thisSchedule;
//public properties
public Schedule ThisSchedule
{
get { return _thisSchedule; }
set
{
if (value != _thisSchedule)
{
NotifyPropertyChanged("ThisSchedule");
}
_thisSchedule = value;
}
}
public List<string> Frequency = new List<string>();
public string Test;
//constructors
public ScheduleViewModel()
{
Frequency.AddRange(new string[] { "Daily", "Weekly", "Monthly" });
Test = "This is only a test.";
}
//INotifyPropertyChanged Implementation
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (null != handler)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
Here's the entire XAML:
<UserControl x:Class="SchedulerUI.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<Grid x:Name="LayoutRoot" Background="White" Loaded="LayoutRoot_Loaded">
<ComboBox Height="23" HorizontalAlignment="Left" Margin="34,41,0,0" Name="comboBox1" Loaded ="comboBox1_Loaded" VerticalAlignment="Top" Width="120" ItemsSource="{Binding Frequency}" />
<TextBox BorderBrush="Black" HorizontalAlignment="Left" Margin="34,41,0,0" Width="100" Height="100" DataContext="LayoutRoot.DataContext" Text="{Binding Test}" />
</Grid>
</UserControl>
Here's the entire codebehind:
namespace SchedulerUI
{
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
App.SchedulerVM = new ScheduleViewModel();
comboBox1.DataContext = App.SchedulerVM;
List<string> testlist = App.SchedulerVM.Frequency;
string teststring = App.SchedulerVM.Test;
}
private void LayoutRoot_Loaded(object sender, RoutedEventArgs e)
{
//App.SchedulerVM = new ScheduleViewModel();
//var root = sender as Grid;
//if (root != null)
//{
// root.DataContext = App.SchedulerVM;
//}
}
private void comboBox1_Loaded(object sender, RoutedEventArgs e)
{
//var combo = sender as ComboBox;
//App.SchedulerVM = new ScheduleViewModel();
//combo.DataContext = App.SchedulerVM;
//combo.ItemsSource = App.SchedulerVM.Frequency;
}
}
}
You binding is not working, because:
when you set ItemsSource in XAML its get executed first and it tries to bind the wrong/empty DataContext
then the Loaded event is raised which will set the correct DataContext but your already existing binding won't be refreshed automatically.
If you have to set the DataContext in the codebehind do it in your views constructor:
public YourView()
{
InitializeComponent();
combo.DataContext = App.SchedulerVM;
}
Then the following binding should work:
<ComboBox Name="comboBox1" ItemsSource="{Binding Frequency}" />
The databinding in WPF/Silverlight needs public properties. Currently Frequency is a public field on your viewmodel change it to a property and everthing should work:
private List<string> frequency = new List<string>();
public List<string> Frequency { get { return frequency; } set { frequency = value; }
And that is why it worked your initial loaded event because you didn't used databind there but you just set the combo.ItemsSource.
I've been looking for hours but I can't find anything useful. Any help appreciated!
I'm writing a Kinect application using WPF with the Coding4Fun toolkit and the MVVM pattern.
I'd like to put all my kinect related logic in my ViewModel and bind those methods to a HoverButton (found in the C4F toolkit).
A normal button had the 'Command' property, However the HoverButton does not.
So in short:
I want to bind the click event of a HoverButton to a method in my ViewModel.
My XAML:
<Window x:Class="KinectTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:fun="clr-namespace:Coding4Fun.Kinect.Wpf.Controls;assembly=Coding4Fun.Kinect.Wpf" Title="MainWindow" Height="350" Width="525"
Loaded="WindowLoaded"
Closed="WindowClosed"
Cursor="None"
>
<Grid Name="MainGrid" MouseMove="GridHoverMouseMove" DataContext="_viewModel">
<Canvas Name="SkeletonCanvas" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Background="Black">
<fun:HoverButton Name="KinectUp" ImageSource="/Images/blue_glass.png" ActiveImageSource="/Images/blue_glass.png" ImageSize="100" Canvas.Top="26" TimeInterval="1000">
</fun:HoverButton>
<fun:HoverButton Name="KinectDown" ImageSource="/Images/red_glass.png" ActiveImageSource="/Images/red_glass.png" ImageSize="100" Canvas.Bottom="26" TimeInterval="1000"/>
</Canvas>
<Image Name="ColorImage" VerticalAlignment="Bottom" HorizontalAlignment="Right" Width="120" Height="120"></Image>
<TextBlock Name="Notification" Foreground="White" FontSize="50" VerticalAlignment="Top" HorizontalAlignment="Stretch" TextAlignment="Center" Text="{Binding Path=Notification, Mode=TwoWay}"></TextBlock>
<Canvas Name="CanvMouse" Background="Transparent" VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
<Image Name="imgMouse" Width="70" Source="/Images/handround_green.png"></Image>
</Canvas>
</Grid>
</Window>
My ViewModel:
internal class MainViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public ICommand KinectUpClick { get; private set; }
public ICommand KinectDownClick { get; private set; }
private string _notification = "Hello";
public SensorHelper SensorHelper { get; set; }
public string Notification
{
get { return _notification; }
set
{
_notification = value;
PropertyChanged(this, new PropertyChangedEventArgs("Notification"));
}
}
public MainViewModel()
{
KinectUpClick = new RelayCommand(PerformKinectUpClick, CanKinectUpClick);
KinectDownClick = new RelayCommand(PerformKinectDownClick, CanKinectDownClick);
}
private bool CanKinectUpClick(object parameter)
{
return SensorHelper.CanMoveUp;
}
private bool CanKinectDownClick(object parameter)
{
return SensorHelper.CanMoveDown;
}
private void PerformKinectUpClick(object parameter)
{
ThreadPool.QueueUserWorkItem((o) =>
{
Notification = "Kinect goes up!";
SensorHelper.MoveAngle(5);
Notification = "Kinect ready...";
});
}
private void PerformKinectDownClick(object parameter)
{
ThreadPool.QueueUserWorkItem((o) =>
{
Notification = "Kinect goes down!";
SensorHelper.MoveAngle(-5);
Notification = "Kinect ready...";
});
;
}
}
}
My Code-behind:
public partial class MainWindow : Window
{
private readonly MainViewModel _viewModel;
private SensorHelper _sensorHelper;
public MainWindow()
{
InitializeComponent();
_viewModel = new MainViewModel();
MainGrid.DataContext = _viewModel;
}
private void WindowLoaded(object sender, RoutedEventArgs e)
{
try
{
_sensorHelper = new SensorHelper();
}
catch (Exception exception)
{
MessageBox.Show(exception.Message);
}
_viewModel.SensorHelper = _sensorHelper;
_sensorHelper.InitializeSkeleton(ref SkeletonCanvas);
//_sensorHelper.CaptureMouse(CanvMouse);
_sensorHelper.InitializeColorFrame(ColorImage);
_sensorHelper.StartKinect();
}
private void WindowClosed(object sender, EventArgs eventArgs)
{
_sensorHelper.StopKinect();
}
private void GridHoverMouseMove(object sender, MouseEventArgs e)
{
imgMouse.SetValue(Canvas.LeftProperty, e.GetPosition(CanvMouse).X - imgMouse.ActualWidth/2);
imgMouse.SetValue(Canvas.TopProperty, e.GetPosition(CanvMouse).Y - imgMouse.ActualHeight/2);
MouseHelper.CheckButton(KinectUp, imgMouse);
MouseHelper.CheckButton(KinectDown, imgMouse);
}
}
Okay, very simple, what you'll have to do is bind an ICommand/Command to an Event by using an EventToCommand which can be found in the MVVMLight Toolkit.
You can use Blend for that as well
Simple example :
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" xmlns:GalaSoft_MvvmLight_Command="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Extras.WPF4"
x:Class="TestProject.Window5"
x:Name="Window"
Title="Window5"
Width="640" Height="480">
<Grid x:Name="LayoutRoot">
<Button Content="Button" HorizontalAlignment="Left" Height="69" Margin="92,117,0,0" VerticalAlignment="Top" Width="206">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<GalaSoft_MvvmLight_Command:EventToCommand Command="{Binding Kinect.MyCommand, Source={StaticResource Locator}}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
</Grid>
</Window>