I am working on a WPF application. I need a player to play a file but I need the player in ViewModel as I need it to create snapshot on the player from the ViewModel. This is not working as I can hear the sound but the video is blank.
What am I Missing.
<ContentControl HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Content="{DynamicResource IconSnapShot}" />
Full XAML
<Window x:Class="TestPlayer.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:TestPlayer"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Grid>
<ContentControl Content="{Binding PlayerFrameworkElement, NotifyOnSourceUpdated=True}" Visibility="{Binding PlayerVisibility}" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Background="Red"/>
</Grid>
</Grid>
</Window>
ViewModel
using Gu.Wpf.Media;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
public class PlayerModelView : NotifyPropertyChanged
{
private MediaElementWrapper playerFrameworkElement = new MediaElementWrapper();
private Uri sourcePath;
public PlayerModelView()
{
this.SourcePath = new Uri(#"J:\file.mkv", UriKind.Absolute);
}
public Uri SourcePath
{
get
{
return this.sourcePath;
}
set
{
this.sourcePath = value;
this.PlayerFrameworkElement.Source = null;
this.PlayerFrameworkElement.Source = value;
}
}
public MediaElementWrapper PlayerFrameworkElement
{
get
{
return this.playerFrameworkElement;
}
set
{
this.playerFrameworkElement = value;
this.RaisePropertyChanged(() => this.PlayerFrameworkElement);
}
}
}
}
// --------------------------------------------------------------------------------------------------------------------
//
//
//
// --------------------------------------------------------------------------------------------------------------------
#region
using System;
using System.ComponentModel;
using System.Linq.Expressions;
#endregion
/// <summary>
/// The notify property changed.
/// </summary>
public abstract class NotifyPropertyChanged : INotifyPropertyChanged
{
#region Public Events
/// <summary>
/// The property changed.
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
#endregion
#region Methods
/// <summary>
/// The raise property changed.
/// </summary>
/// <param name="propertyExpression">
/// The property expression.
/// </param>
/// <typeparam name="TProperty">
/// </typeparam>
protected void RaisePropertyChanged<TProperty>(Expression<Func<TProperty>> propertyExpression)
{
var memberExpression = (MemberExpression)propertyExpression.Body;
var propertyName = memberExpression.Member.Name;
RaisePropertyChanged(propertyName);
}
/// <summary>
/// The raise property changed.
/// </summary>
/// <param name="propertyName">
/// The property name.
/// </param>
private void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
Related
I want to bind some properties (FooClass.FooString) of a custom class (FooClass) to my MainWindow. Now below (Working known behavior) is the default working solution if binding some data to a gui.
What I want to do is in the second code block (Not working, but desired behavior). Expose some properties of another class objectto the gui and update it.
**Problem**: TheTestStringis not getting updated (on the gui, code behind works). ThePropertyChangedeventis alsonull` (not subscribed?!).
Is this the wrong way how to bind data?
If I bind the complete FooClass object to the gui and set Path (of TextBlock) to Foo.FooString, the gui and string is updated. But I don't want to do it this way.
Is this the way how to solve it?
Working known behavior
public partial class MainWindow : Window
{
public FooClass Foo { get; } = new FooClass();
public MainWindow()
{
DataContext = this;
InitializeComponent();
Loaded += _OnLoaded;
}
private async void _OnLoaded(object sender, RoutedEventArgs e)
{
await Task.Delay(1000);
Foo.ChangeTheProperty();
}
}
public class FooClass : INotifyPropertyChanged
{
public string FooString
{
get => _FooString;
set
{
if (_FooString == value) return;
_FooString = value;
OnPropertyChanged();
}
}
private string _FooString = "empty";
public void ChangeTheProperty()
{
FooString = "Loaded";
}
// ##############################################################################################################################
// PropertyChanged
// ##############################################################################################################################
#region PropertyChanged
/// <summary>
/// The PropertyChanged Eventhandler
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Raise/invoke the propertyChanged event!
/// </summary>
/// <param name="propertyName"></param>
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
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"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance local:MainWindow}"
Title="MainWindow" Height="450" Width="800">
<Grid>
<TextBlock Text="{Binding Path=Foo.FooString}" VerticalAlignment="Center" HorizontalAlignment="Center"/>
</Grid>
</Window>
Not working, but desired behavior
public partial class MainWindow : Window
{
public string TestString => _Foo.FooString;
private readonly FooClass _Foo;
public MainWindow()
{
_Foo = new FooClass();
DataContext = this;
InitializeComponent();
Loaded += _OnLoaded;
}
private async void _OnLoaded(object sender, RoutedEventArgs e)
{
await Task.Delay(1000);
_Foo.ChangeTheProperty();
}
}
public class FooClass : INotifyPropertyChanged
{
public string FooString
{
get => _FooString;
set
{
if (_FooString == value) return;
_FooString = value;
OnPropertyChanged();
}
}
private string _FooString = "empty";
public void ChangeTheProperty()
{
FooString = "Loaded";
}
// ##############################################################################################################################
// PropertyChanged
// ##############################################################################################################################
#region PropertyChanged
/// <summary>
/// The PropertyChanged Eventhandler
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Raise/invoke the propertyChanged event!
/// </summary>
/// <param name="propertyName"></param>
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
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"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance local:MainWindow}"
Title="MainWindow" Height="450" Width="800">
<Grid>
<TextBlock Text="{Binding Path=TestString}" VerticalAlignment="Center" HorizontalAlignment="Center"/>
</Grid>
</Window>
Solution 1
Subscribe to the Foo.PropertyChanged event and route it to MainWindow.PropertyChanged.
public partial class MainWindow : Window, INotifyPropertyChanged
{
public FooClass Foo { get; } = new FooClass();
public MainWindow()
{
Foo.PropertyChanged += (sender, args) => OnPropertyChanged(args.PropertyName);
DataContext = this;
InitializeComponent();
Loaded += _OnLoaded;
}
private async void _OnLoaded(object sender, RoutedEventArgs e)
{
await Task.Delay(1000);
Foo.ChangeTheProperty();
}
// ##############################################################################################################################
// PropertyChanged
// ##############################################################################################################################
#region PropertyChanged
/// <summary>
/// The PropertyChanged Eventhandler
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Raise/invoke the propertyChanged event!
/// </summary>
/// <param name="propertyName"></param>
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
I might not have fully understood what you want, but here is a working example of data binding, that is somewhat close to your example.
The two main changes were:
Set the datacontext to the VM and not the code behind
Actually give OnPropertyChanged the argument it needs to correctly trigger the refresh, the name of the property.
Result:
MainWindow.xaml
<Window x:Class="ListViewColor.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="450" Width="800">
<Grid>
<TextBlock Text="{Binding Foo.FooString}" VerticalAlignment="Center" HorizontalAlignment="Center" Background="Aqua"/>
</Grid>
</Window>
MainWindow.xaml.cs
namespace ListViewColor
{
public partial class MainWindow : Window
{
public FooClass Foo { get; } = new FooClass();
public MainWindow()
{
DataContext = this;
InitializeComponent();
Loaded += _OnLoaded;
}
private async void _OnLoaded(object sender, RoutedEventArgs e)
{
await Task.Delay(1000);
Foo.ChangeTheProperty();
}
}
}
FooClass.cs
using System.ComponentModel;
using System.Runtime.CompilerServices;
public class FooClass : INotifyPropertyChanged
{
private string _FooString = "Empty";
public string FooString
{
get
{
return _FooString;
}
set
{
if (_FooString == value) return;
_FooString = value;
OnPropertyChanged();
}
}
public void ChangeTheProperty()
{
FooString = "Loaded";
}
#region PropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
I hope that helps!
I have spent quite some time on this so seeking help.
Simple data binding with usercontrol with mvvm light does not work.
I have done the following.
Created a MvvmLight (WPF451) project using VS 2015 and named it WpfDataBindingUserControlT1
Added a UserControl and renamed it to SimpleUserControl.xaml
Added some lables as a children(wraped in stackpanel) to the grid inside SimpleUserControl.xaml(All code is given below)
Added a dependency properties in the code behind of the SimpleUserControl.xaml(SimpleUserControl.cs) so that these will help me in databinding.
The data binding simply does not work. I have pulled half of my hair on this so please help. I guess I am missing very simple on this.
The code is as follows.
MainWindows.xaml
<Window x:Class="WpfDataBindingUserControlT1.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:ignore="http://www.galasoft.ch/ignore"
xmlns:local="clr-namespace:WpfDataBindingUserControlT1"
mc:Ignorable="d ignore"
Height="400"
Width="300"
Title="MVVM Light Application"
DataContext="{Binding Main, Source={StaticResource Locator}}">
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Skins/MainSkin.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
<Grid x:Name="LayoutRoot">
<TextBlock FontSize="36"
FontWeight="Bold"
Foreground="Purple"
Text="{Binding WelcomeTitle}"
VerticalAlignment="Center"
HorizontalAlignment="Center"
TextWrapping="Wrap" />
<local:SimpleUserControl DataContext="{Binding RelativeSource={RelativeSource Self}}" CellValue="{Binding WelcomeTitle}" />
</Grid>
</Window>
MainWindow.cs ( I did not change any thing in this file.)
using System.Windows;
using WpfDataBindingUserControlT1.ViewModel;
namespace WpfDataBindingUserControlT1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
/// <summary>
/// Initializes a new instance of the MainWindow class.
/// </summary>
public MainWindow()
{
InitializeComponent();
Closing += (s, e) => ViewModelLocator.Cleanup();
}
}
}
SimpleUserControl.xaml
<UserControl x:Class="WpfDataBindingUserControlT1.SimpleUserControl"
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:WpfDataBindingUserControlT1"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<StackPanel>
<Label Content="This Prints" />
<Label Name="MyLable" Content="{Binding Path=CellValue}"></Label>
<Label Content="This also Prints" />
</StackPanel>
</Grid>
</UserControl>
SimpleUserControl.cs (added a dependency prop)
using System.Windows;
using System.Windows.Controls;
namespace WpfDataBindingUserControlT1
{
/// <summary>
/// Interaction logic for SimpleUserControl.xaml
/// </summary>
public partial class SimpleUserControl : UserControl
{
public string CellValue
{
get { return (string)GetValue(CellValueProperty); }
set { SetValue(CellValueProperty, value); }
}
public static readonly DependencyProperty CellValueProperty =
DependencyProperty.Register("CellValue", typeof(string), typeof(SimpleUserControl), new FrameworkPropertyMetadata
{
BindsTwoWayByDefault = true,
});
public SimpleUserControl()
{
InitializeComponent();
}
}
}
MainViewModel.cs (I have not changed any thing in this)
using GalaSoft.MvvmLight;
using WpfDataBindingUserControlT1.Model;
namespace WpfDataBindingUserControlT1.ViewModel
{
/// <summary>
/// This class contains properties that the main View can data bind to.
/// <para>
/// See http://www.mvvmlight.net
/// </para>
/// </summary>
public class MainViewModel : ViewModelBase
{
private readonly IDataService _dataService;
/// <summary>
/// The <see cref="WelcomeTitle" /> property's name.
/// </summary>
public const string WelcomeTitlePropertyName = "WelcomeTitle";
private string _welcomeTitle = string.Empty;
/// <summary>
/// Gets the WelcomeTitle property.
/// Changes to that property's value raise the PropertyChanged event.
/// </summary>
public string WelcomeTitle
{
get
{
return _welcomeTitle;
}
set
{
Set(ref _welcomeTitle, value);
}
}
/// <summary>
/// Initializes a new instance of the MainViewModel class.
/// </summary>
public MainViewModel(IDataService dataService)
{
_dataService = dataService;
_dataService.GetData(
(item, error) =>
{
if (error != null)
{
// Report error here
return;
}
WelcomeTitle = item.Title;
});
}
////public override void Cleanup()
////{
//// // Clean up if needed
//// base.Cleanup();
////}
}
}
ViewModelLocator.cs (I have not changed any thing in this as well.)
/*
In App.xaml:
<Application.Resources>
<vm:ViewModelLocatorTemplate xmlns:vm="clr-namespace:WpfDataBindingUserControlT1.ViewModel"
x:Key="Locator" />
</Application.Resources>
In the View:
DataContext="{Binding Source={StaticResource Locator}, Path=ViewModelName}"
*/
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Ioc;
using Microsoft.Practices.ServiceLocation;
using WpfDataBindingUserControlT1.Model;
namespace WpfDataBindingUserControlT1.ViewModel
{
/// <summary>
/// This class contains static references to all the view models in the
/// application and provides an entry point for the bindings.
/// <para>
/// See http://www.mvvmlight.net
/// </para>
/// </summary>
public class ViewModelLocator
{
static ViewModelLocator()
{
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
if (ViewModelBase.IsInDesignModeStatic)
{
SimpleIoc.Default.Register<IDataService, Design.DesignDataService>();
}
else
{
SimpleIoc.Default.Register<IDataService, DataService>();
}
SimpleIoc.Default.Register<MainViewModel>();
}
/// <summary>
/// Gets the Main property.
/// </summary>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance",
"CA1822:MarkMembersAsStatic",
Justification = "This non-static member is needed for data binding purposes.")]
public MainViewModel Main
{
get
{
return ServiceLocator.Current.GetInstance<MainViewModel>();
}
}
/// <summary>
/// Cleans up all the resources.
/// </summary>
public static void Cleanup()
{
}
}
}
Add this line to your SimpleUserControl.cs constructor
public SimpleUserControl()
{
InitializeComponent();
(this.Content as FrameworkElement).DataContext = this;
}
You're basically setting the DataContext of the first element in the UserControl.
Jerry Nixon has a great article on this here
UPDATE
forgot to add get rid of the RelativeSource eg
<local:SimpleUserControl CellValue="{Binding WelcomeTitle}" />
Can anyone show me a simple working example for a WPF MVVM application to set the ItemsSource of combobox B based on the SelectedItem of ComboBox A?
It seems from what I've found on this site that it gets all too complicated all too quickly.
What's the "right" MVVM way to get it done?
Thank you.
EDIT
I updated using Didier's example.
An extract of my XAML:
<ComboBox Name="BrowserStackDesktopOS" ItemsSource="Binding Platforms.AvailableBrowserStackDesktopOSes}" SelectedIndex="0" SelectedItem="{Binding Platforms.BrowserStackDesktopOSSelectedValue, Mode=TwoWay}"/>
<ComboBox Name="BrowserStackDesktopOSVersion" ItemsSource="{Binding Platforms.AvailableBrowserStackDesktopOSVersions}" SelectedIndex="0" SelectedItem="{Binding Platforms.BrowserStackDesktopOSVersionSelectedValue, Mode=TwoWay}"/>
<ComboBox Name="BrowserStackDesktopBrowser" ItemsSource="{Binding Platforms.AvailableBrowserStackDesktopBrowsers}" SelectedIndex="0" SelectedItem="{Binding Platforms.BrowserStackDesktopBrowserSelectedValue, Mode=TwoWay}"/>
<ComboBox Name="BrowserStackDesktopBrowserVersion" ItemsSource="{Binding Platforms.AvailableBrowserStackDesktopBrowserVersions}" SelectedIndex="0" SelectedItem="{Binding Platforms.BrowserStackDesktopBrowserVersionSelectedValue, Mode=TwoWay}"/>
And an example of my code behind:
public string BrowserStackDesktopOSSelectedValue {
get { return (string)GetValue(BrowserStackDesktopOSSelectedValueProperty); }
set { SetValue(BrowserStackDesktopOSSelectedValueProperty, value);
AvailableBrowserStackDesktopOSVersions = AvailableBrowserStackDesktopPlatforms.GetOSVersions(BrowserStackDesktopOSSelectedValue);
NotifyPropertyChanged("BrowserStackDesktopOSSelectedValue");
}
}
However when I select a value for the first ComboBox nothing happens. I am wanting the Itemsource of the next ComboBox to by populated.
What have I done wrong?
Basically you need to expose in your MVVM 2 collections of values for combo-box choices and two properties for selected values.
In the beginning only the first collection if filled with values. When the first selected value changes the second collection will be filled in with appropriate values. Here is an example implementation:
Code behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
//Set the data context of the window
DataContext = new TestVM();
}
}
public class TestVM : INotifyPropertyChanged
{
#region Class attributes
protected static string[] firstComboValues = new string[] { "Choice_1", "Choice_2" };
protected static string[][] secondComboValues =
new string[][] {
new string[] { "value_1_1", "value_1_2", "value_1_3" },
new string[] { "value_2_1", "value_2_2", "value_2_3" }
};
#endregion
#region Public Properties
#region FirstSelectedValue
protected string m_FirstSelectedValue;
/// <summary>
///
/// </summary>
public string FirstSelectedValue
{
get { return m_FirstSelectedValue; }
set
{
if (m_FirstSelectedValue != value)
{
m_FirstSelectedValue = value;
UpdateSecondComboValues();
NotifyPropertyChanged("FirstSelectedValue");
}
}
}
#endregion
#region SecondSelectedValue
protected string m_SecondSelectedValue;
/// <summary>
///
/// </summary>
public string SecondSelectedValue
{
get { return m_SecondSelectedValue; }
set
{
if (m_SecondSelectedValue != value)
{
m_SecondSelectedValue = value;
NotifyPropertyChanged("SecondSelectedValue");
}
}
}
#endregion
#region FirstComboValues
protected ObservableCollection<string> m_FirstComboValues;
/// <summary>
///
/// </summary>
public ObservableCollection<string> FirstComboValues
{
get { return m_FirstComboValues; }
set
{
if (m_FirstComboValues != value)
{
m_FirstComboValues = value;
NotifyPropertyChanged("FirstComboValues");
}
}
}
#endregion
#region SecondComboValues
protected ObservableCollection<string> m_SecondComboValues;
/// <summary>
///
/// </summary>
public ObservableCollection<string> SecondComboValues
{
get { return m_SecondComboValues; }
set
{
if (m_SecondComboValues != value)
{
m_SecondComboValues = value;
NotifyPropertyChanged("SecondComboValues");
}
}
}
#endregion
#endregion
public TestVM()
{
FirstComboValues = new ObservableCollection<string>(firstComboValues);
}
/// <summary>
/// Update the collection of values for the second combo box
/// </summary>
protected void UpdateSecondComboValues()
{
int firstComboChoice;
for (firstComboChoice = 0; firstComboChoice < firstComboValues.Length; firstComboChoice++)
{
if (firstComboValues[firstComboChoice] == FirstSelectedValue)
break;
}
if (firstComboChoice == firstComboValues.Length)// just in case of a bug
SecondComboValues = null;
else
SecondComboValues = new ObservableCollection<string>(secondComboValues[firstComboChoice]);
}
#region INotifyPropertyChanged implementation
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
And the associated XAML
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Name="window" x:Class="Testing1.MainWindow">
<Grid>
<Grid HorizontalAlignment="Center" VerticalAlignment="Center" Width=" 300">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="10"/>
<RowDefinition/>
</Grid.RowDefinitions>
<ComboBox x:Name="FirstOne" ItemsSource="{Binding FirstComboValues}" SelectedItem="{Binding FirstSelectedValue, Mode=TwoWay}"/>
<ComboBox x:Name="SecondOne" ItemsSource="{Binding SecondComboValues}" SelectedItem="{Binding SecondSelectedValue, Mode=TwoWay}" Grid.Row="2"/>
</Grid>
</Grid>
</Window>
As you can see the SelectedValue properties of combo boxes are binded in TwoWay mode so when SelectedValue property of the combo box changes it changes the value on the VM side. And in FirstSelectedValue property setter UpdateSecondComboValues() method is called to update values for the second combo box.
EDIT:
It happens because you mixed both INotifPropertyChanged and DependencyObject. You should choose one of them. Usually you implement INotifyPropertyChanged in your VM and the code in the property setter will work.
If you inherit from DependencyObject however, you should not write any code in the setter/getter. It will never be called by the TwoWay binding. It will just call GetValue(...) internally. To be able to execute an action on DependencyProperty change you should declare it differently with a property changed handler:
#region BrowserStackDesktopOSSelectedValue
/// <summary>
/// BrowserStackDesktopOSSelectedValue Dependency Property
/// </summary>
public static readonly DependencyProperty BrowserStackDesktopOSSelectedValue Property =
DependencyProperty.Register("BrowserStackDesktopOSSelectedValue ", typeof(string), typeof(YourVM),
new FrameworkPropertyMetadata((string)null,
new PropertyChangedCallback(OnBrowserStackDesktopOSSelectedValue Changed)));
/// <summary>
/// Gets or sets the BrowserStackDesktopOSSelectedValue property. This dependency property
/// indicates ....
/// </summary>
public string BrowserStackDesktopOSSelectedValue
{
get { return (string)GetValue(BrowserStackDesktopOSSelectedValue Property); }
set { SetValue(BrowserStackDesktopOSSelectedValue Property, value); }
}
/// <summary>
/// Handles changes to the BrowserStackDesktopOSSelectedValue property.
/// </summary>
private static void OnBrowserStackDesktopOSSelectedValue Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
YourVM target = (YourVM)d;
string oldBrowserStackDesktopOSSelectedValue = (string)e.OldValue;
string newBrowserStackDesktopOSSelectedValue = target.BrowserStackDesktopOSSelectedValue ;
target.OnBrowserStackDesktopOSSelectedValue Changed(oldBrowserStackDesktopOSSelectedValue , newBrowserStackDesktopOSSelectedValue );
}
/// <summary>
/// Provides derived classes an opportunity to handle changes to the BrowserStackDesktopOSSelectedValue property.
/// </summary>
protected virtual void OnBrowserStackDesktopOSSelectedValue Changed(string oldBrowserStackDesktopOSSelectedValue , string newBrowserStackDesktopOSSelectedValue )
{
//Here write some code to update your second ComboBox content.
AvailableBrowserStackDesktopOSVersions = AvailableBrowserStackDesktopPlatforms.GetOSVersions(BrowserStackDesktopOSSelectedValue);
}
#endregion
By the way I always use Dr WPF snippets to write DPs so it goes much faster.
is there an issue with two way binding to the IsChecked property on a ToggleButton in .NET 3.5?
I have this XAML:
<ToggleButton
Name="tbMeo"
Command="{Binding FilterOnSatelliteTypeCmd}"
IsChecked="{Binding ShowMeoDataOnly, Mode=TwoWay}"
ToolTip="Show MEO data only">
<Image Source="../images/32x32/Filter_Meo.png" Height="16" />
</ToggleButton>
I have a ViewModel with the following property:
private bool _showMeoDataOnly;
public bool ShowMeoDataOnly
{
get { return _showMeoDataOnly; }
set
{
if (_showMeoDataOnly != value)
{
_showMeoDataOnly = value;
RaisePropertyChangedEvent("ShowMeoDataOnly");
}
}
}
If I click on the ToggleButton, the value of ShowMeoDataOnly is set accordingly. However, if I set ShowMeoDataOnly to true from code behind, the ToggleButton's visual state does not change to indicate that IsChecked is true. However, if I manually set the ToggleButton's IsChecked property instead of setting ShowMeoDataOnly to true in code behind, the button's visual state changes accordingly.
Unfortunately, switching over to .NET 4/4.5 is not an option right now, so I cannot confirm if this is a .NET 3.5 problem.
Is there anything wrong with my code?
Using a .NET 3.5 project to test this and the binding seems to work for me. Do you have INotifyPropertyChanged implemented on your ViewModel and use it appropriately when ShowMeoDataOnly gets set? You didn't post all your code, so it's hard to tell what the ViewModel is doing.
Here's what I have that worked. When I run the application, the button is selected. That's because ViewModelBase implements INotifyPropertyChanged and I do base.OnPropertyChanged("ShowMeoDataOnly") when the property is set.
MainWindow.xaml
<Window x:Class="ToggleButtonIsCheckedBinding.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ToggleButtonIsCheckedBinding"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:MainWindowViewModel />
</Window.DataContext>
<Grid>
<ToggleButton IsChecked="{Binding ShowMeoDataOnly, Mode=TwoWay}">
Show Meo Data Only
</ToggleButton>
</Grid>
</Window>
MainWindowViewModel.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ToggleButtonIsCheckedBinding
{
class MainWindowViewModel : ViewModelBase
{
bool _showMeoDataOnly;
public bool ShowMeoDataOnly {
get
{
return _showMeoDataOnly;
}
set
{
_showMeoDataOnly = value;
base.OnPropertyChanged("ShowMeoDataOnly");
}
}
public MainWindowViewModel()
{
ShowMeoDataOnly = true;
}
}
}
ViewModelBase.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.ComponentModel;
namespace ToggleButtonIsCheckedBinding
{
/// <summary>
/// Base class for all ViewModel classes in the application.
/// It provides support for property change notifications
/// and has a DisplayName property. This class is abstract.
/// </summary>
public abstract class ViewModelBase : INotifyPropertyChanged, IDisposable
{
#region Constructor
protected ViewModelBase()
{
}
#endregion // Constructor
#region DisplayName
/// <summary>
/// Returns the user-friendly name of this object.
/// Child classes can set this property to a new value,
/// or override it to determine the value on-demand.
/// </summary>
public virtual string DisplayName { get; protected set; }
#endregion // DisplayName
#region Debugging Aides
/// <summary>
/// Warns the developer if this object does not have
/// a public property with the specified name. This
/// method does not exist in a Release build.
/// </summary>
[Conditional("DEBUG")]
[DebuggerStepThrough]
public void VerifyPropertyName(string propertyName)
{
// Verify that the property name matches a real,
// public, instance property on this object.
if (TypeDescriptor.GetProperties(this)[propertyName] == null)
{
string msg = "Invalid property name: " + propertyName;
if (this.ThrowOnInvalidPropertyName)
throw new Exception(msg);
else
Debug.Fail(msg);
}
}
/// <summary>
/// Returns whether an exception is thrown, or if a Debug.Fail() is used
/// when an invalid property name is passed to the VerifyPropertyName method.
/// The default value is false, but subclasses used by unit tests might
/// override this property's getter to return true.
/// </summary>
protected virtual bool ThrowOnInvalidPropertyName { get; private set; }
#endregion // Debugging Aides
#region INotifyPropertyChanged Members
/// <summary>
/// Raised when a property on this object has a new value.
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Raises this object's PropertyChanged event.
/// </summary>
/// <param name="propertyName">The property that has a new value.</param>
protected virtual void OnPropertyChanged(string propertyName)
{
this.VerifyPropertyName(propertyName);
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
#endregion // INotifyPropertyChanged Members
#region IDisposable Members
/// <summary>
/// Invoked when this object is being removed from the application
/// and will be subject to garbage collection.
/// </summary>
public void Dispose()
{
this.OnDispose();
}
/// <summary>
/// Child classes can override this method to perform
/// clean-up logic, such as removing event handlers.
/// </summary>
protected virtual void OnDispose()
{
}
#if DEBUG
/// <summary>
/// Useful for ensuring that ViewModel objects are properly garbage collected.
/// </summary>
~ViewModelBase()
{
string msg = string.Format("{0} ({1}) ({2}) Finalized", this.GetType().Name, this.DisplayName, this.GetHashCode());
System.Diagnostics.Debug.WriteLine(msg);
}
#endif
#endregion // IDisposable Members
}
}
(Note: ViewModelBase is pulled from this project: http://msdn.microsoft.com/en-us/magazine/dd419663.aspx )
Verify that you DataContext is set up correctly.
DataContext = this;
... in your MainWindow.xaml.cs constructor is the easiest way, assuming the code we're looking at is in the MainWindow class.
I have a senario here... (Extremely sorry writing such a long post)
I have are TreeView(Bound to the observable collection of Phones(Different types)) i have a contentCOntrol whose COntent Binding is set to the selectedItem for TreeView
Code....
<Border Grid.Column="2">
<ContentControl Name="userControlContentControl"
Content="{Binding ElementName=PhoneTreeViewUserControl,
Path=SelectedItem,
Converter={StaticResource ResourceKey=rightDisplayConverter}}">
<ContentControl.Resources>
<DataTemplate DataType="{x:Type EntityLayer:Settings}">
<ViewLayer:SettingsView />
</DataTemplate>
<DataTemplate DataType="{x:Type EntityLayer:CandyBarPhones}">
<ViewLayer:CandyBarPhonesListView />
</DataTemplate>
<DataTemplate DataType="{x:Type EntityLayer:CandyBarPhone}">
<ViewLayer:CandyBarPhoneView />
</DataTemplate>
<DataTemplate DataType="{x:Type EntityLayer:QwertyPhones}">
<ViewLayer:QwertyPhonesListView />
</DataTemplate>
<DataTemplate DataType="{x:Type EntityLayer:QuertyPhone}">
<ViewLayer:QuertyPhoneView />
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
</Border>
Problem is that when i select a Phone from TreeView(a specific View is populated from contentcontrol) I wish to pass UniqueId(PhoneBase has that Property) to my ViewModel of View and also fire a function in viewModel so that it can get Data from BusinessLayer... and Initialize the ViewModel and all its Properties.
CodeBehind for UserControl
region class - QuertyPhoneView
/// <summary>
/// Interaction logic for ProductIdEditorView.xaml
/// </summary>
public partial class QuertyPhoneView : BaseUserControl
{
QertyPhoneViewModel quertyPhoneViewModel;
#region Constructor
/// <summary>
/// </summary>
public ProductIdEditorView()
{
InitializeComponent();
quertyPhoneViewModel =
new quertyPhoneViewModel ();
this.DataContext = quertyPhoneViewModel;
}
# endregion
}
#endregion
Also in ViewModel I have Messenger Registrations .... but Every time i selected another phone type and then select the former type the messenger are registered again without deregistering Earlier... (I dont have any Deregister method in Messenger, using Marlon's Mediator V2) and its making application Very Slow if used for an 15-20 min or so
ViewModel for a Typical View..
region class - QuertyPhoneViewModel
/// <summary>
/// QuertyPhoneViewModel
/// </summary>
public class QuertyPhoneViewModel : BaseViewModel
{
# region Member Variables
/// <summary>
/// quertyPhoneDetails
/// </summary>
private QuertyPhone quertyPhoneDetails;
/// <summary>
/// oldQuertyPhoneDetails
/// </summary>
private ProductId oldQuertyPhoneDetails;
/// <summary>
/// productIds
/// </summary>
private QuertyPhones quertyPhones;
/// <summary>
/// productIdModel
/// </summary>
private readonly QuertyPhoneModal quertyPhoneModal;
/// <summary>
/// log
/// </summary>
private static readonly ILog log =
LogManager.GetLogger(typeof (QuertyPhoneViewModel));
/// <summary>
/// selectedCalTblName
/// </summary>
private CalibrationTable selectedCalTblName;
# endregion
# region Constructor
/// <summary>
/// QuertyPhoneViewModel
/// </summary>
public QuertyPhoneViewModel()
{
RegisterForMessage();
quertyPhoneModal= new QuertyPhoneModal();
if (QuertyPhoneDetails == null)
{
//Requesting TreeViewUsersontrol To send Details
// I wish to remove these registrations
Messenger.NotifyColleagues<ProductId>(
MessengerMessages.SEND_SELECTED_PHONE, QuertyPhoneDetails);
}
CancelPhoneDetailsChangeCommand = new RelayCommand(CancelQuertyPhoneDetailsChanges,
obj => this.IsDirty);
SavePhoneDetailsChangeCommand = new RelayCommand(SaveQuertyPhoneDetailsToTree,
CanSaveQuertyPhoneDetailsToTree);
}
# endregion
# region Properties
/// <summary>
/// CalibrationTblNameList
/// </summary>
public ObservableCollection<CalibrationTable> CalibrationTblNameList { get; set; }
/// <summary>
/// CancelPhoneDetailsChangeCommand
/// </summary>
public ICommand CancelPhoneDetailsChangeCommand { get; set; }
/// <summary>
/// SavePhoneDetailsChangeCommand
/// </summary>
public ICommand SavePhoneDetailsChangeCommand { get; set; }
/// <summary>
/// QuertyPhoneDetails
/// </summary>
public ProductId QuertyPhoneDetails
{
get { return quertyPhoneDetails; }
set
{
quertyPhoneDetails = value;
OnPropertyChanged("QuertyPhoneDetails");
}
}
/// <summary>
/// SelectedCalTblName
/// </summary>
public CalibrationTable SelectedCalTblName
{
get { return selectedCalTblName; }
set
{
selectedCalTblName = value;
OnPropertyChanged("SelectedCalTblName");
if (selectedCalTblName != null)
{
QuertyPhoneDetails.CalibrationTableId =
selectedCalTblName.UniqueId;
}
}
}
/// <summary>
/// QuertyPhones
/// </summary>
public QuertyPhones QuertyPhones
{
get { return productIds; }
set
{
productIds = value;
OnPropertyChanged("QuertyPhones");
}
}
# endregion
# region Public Methods
# endregion
# region Private Methods
/// <summary>
/// RegisterForMessage
/// I wish to remove these registrations too
/// </summary>
private void RegisterForMessage()
{
log.Debug("RegisterForMessage" + BaseModel.FUNCTION_ENTERED_LOG);
Messenger.Register(MessengerMessages.RECIEVE_SELECTED_PHONE,
(Action<ProductId>) (o =>
{
if (o != null)
{
QuertyPhoneDetails
=
o.Clone() as
ProductId;
AttachChangeEvents
();
oldQuertyPhoneDetails
= o;
SetCalibrationTables
();
}
}));
Messenger.Register(MessengerMessages.REFRESH_PHONEDETILAS,
(Action<string>)
(o =>
{
GetOldQuertyPhoneDetails(o);
this.IsDirty = false;
}));
log.Debug("RegisterForMessage" + BaseModel.FUNCTION_EXIT_LOG);
}
/// <summary>
/// CanSaveQuertyPhoneDetailsToTree
/// </summary>
/// <param name = "obj"></param>
/// <returns></returns>
private bool CanSaveQuertyPhoneDetailsToTree(object obj)
{
return this.IsDirty && ValidateQuertyPhoneDetails();
}
/// <summary>
/// SaveQuertyPhoneDetailsToTree
/// </summary>
/// <param name = "obj"></param>
private void SaveQuertyPhoneDetailsToTree(object obj)
{
log.Debug("SaveQuertyPhoneDetails" + BaseModel.FUNCTION_ENTERED_LOG);
if (Utility.IsStringNullOrEmpty(QuertyPhoneDetails.CalibrationTableId))
{
MessageBoxResult result =
ShowMessageDialog(
"Calibration Table name is not set.Do you wish to proceed?",
"Save ProductId",
MessageBoxButton.YesNo,
MessageBoxImage.Exclamation);
if (result == MessageBoxResult.Yes)
{
SaveDetails();
}
}
else
{
SaveDetails();
}
log.Debug("SaveQuertyPhoneDetails" + BaseModel.FUNCTION_EXIT_LOG);
}
/// <summary>
/// SaveDetails
/// </summary>
private void SaveDetails()
{
log.Debug("SaveDetails" + BaseModel.FUNCTION_ENTERED_LOG);
if (productIdModel.SaveQuertyPhoneDetails(QuertyPhoneDetails))
{
UpdateProgressbarStatus(
"ProductId " + QuertyPhoneDetails.Specs
+ " saved successfully.",
false);
this.IsDirty = false;
}
else
{
ShowMessageDialog(
"ProductId " + QuertyPhoneDetails.Specs +
" not saved successfully.",
"Save ProductId",
MessageBoxButton.OK,
MessageBoxImage.Error);
UpdateProgressbarStatus(
"ProductId " + QuertyPhoneDetails.Specs
+ " not saved successfully.",
false);
}
log.Debug("SaveDetails" + BaseModel.FUNCTION_EXIT_LOG);
}
/// <summary>
/// SetCalibrationTables
/// </summary>
private void SetCalibrationTables()
{
log.Debug("SetCalibrationTables" + BaseModel.FUNCTION_ENTERED_LOG);
CalibrationTblNameList =
productIdModel.FileData.CalibrationTables.CalibrationTableList;
SelectedCalTblName = (CalibrationTblNameList.Where(
calibrationTable =>
calibrationTable.UniqueId.Equals(
QuertyPhoneDetails.CalibrationTableId))).FirstOrDefault();
log.Debug("SetCalibrationTables" + BaseModel.FUNCTION_EXIT_LOG);
}
/// <summary>
/// CancelQuertyPhoneDetailsChanges
/// </summary>
/// <param name = "obj"></param>
private void CancelQuertyPhoneDetailsChanges(object obj)
{
log.Debug("CancelQuertyPhoneDetailsChanges" + BaseModel.FUNCTION_ENTERED_LOG);
GetOldQuertyPhoneDetails(QuertyPhoneDetails.Specs);
this.IsDirty = false;
productIdModel.SelectMainProductList();
Messenger.NotifyColleagues<bool>(
MessengerMessages.POP_UP_CLOSE_REQUEST, true);
log.Debug("CancelQuertyPhoneDetailsChanges" + BaseModel.FUNCTION_EXIT_LOG);
}
/// <summary>
/// GetOldQuertyPhoneDetails
/// </summary>
/// <param name = "idNumber"></param>
private void GetOldQuertyPhoneDetails(string idNumber)
{
log.Debug("GetOldQuertyPhoneDetails" + BaseModel.FUNCTION_ENTERED_LOG);
if (!string.IsNullOrEmpty(idNumber))
{
ProductId tempQuertyPhoneDetails =
productIdModel.GetProduct(idNumber).Clone() as ProductId;
if (tempQuertyPhoneDetails != null)
{
QuertyPhoneDetails = tempQuertyPhoneDetails;
QuertyPhoneDetails.Reset();
}
AttachChangeEvents();
}
log.Debug("GetOldQuertyPhoneDetails" + BaseModel.FUNCTION_EXIT_LOG);
}
/// <summary>
/// AttachChangeEvents
/// </summary>
private void AttachChangeEvents()
{
log.Debug("AttachChangeEvents" + BaseModel.FUNCTION_ENTERED_LOG);
QuertyPhoneDetails.Reset();
QuertyPhoneDetails.PropertyChanged -= OnQuertyPhoneDetailsChanged;
QuertyPhoneDetails.PropertyChanged += OnQuertyPhoneDetailsChanged;
log.Debug("AttachChangeEvents" + BaseModel.FUNCTION_EXIT_LOG);
}
private void OnQuertyPhoneDetailsChanged(object sender,
PropertyChangedEventArgs e)
{
if (QuertyPhoneDetails.Changes.Count > 0)
{
OnItemDataChanged(sender, e, QuertyPhoneDetails.Changes.Count);
}
}
/// <summary>
/// ValidateQuertyPhoneDetails
/// </summary>
/// <returns></returns>
private bool ValidateQuertyPhoneDetails()
{
log.Debug("ValidateQuertyPhoneDetails" + BaseModel.FUNCTION_ENTERED_LOG);
if (
!Utility.IsValidAlphanumeric(
QuertyPhoneDetails.ControllerInformation)
||
(!Utility.IsValidAlphanumeric(QuertyPhoneDetails.PhoneId))
|| (!Utility.IsValidAlphanumeric(QuertyPhoneDetails.Specs)))
{
QuertyPhoneDetails.ErrorMsg =
AddTreeNodeViewModel.ERROR_NO_ALPHANUMERIC;
return false;
}
QuertyPhoneDetails.ErrorMsg = string.Empty;
log.Debug("ValidateQuertyPhoneDetails" + BaseModel.FUNCTION_EXIT_LOG);
return true;
}
# endregion
}
#endregion
I am absolutly Puzzled what to do...
ANy help in this regrad would be GR8!!! Thanks
Why not have your ParentViewModel track the SelectedItem instead of having your View do it? I personally hate doing any kind of business logic in my View. It should just be a pretty layer that sits on top of my ViewModels to make it easier for the user to interact with them.
For example, your PhonesViewModel might have:
// Leaving out get/set method details for simplicity
Observable<QuertyPhone> Phones { get; set; }
QuertyPhone SelectedPhone { get; set; }
ICommand SaveSelectedPhoneCommand { get; }
ICommand CancelSelectedPhoneCommand { get; }
Your View would simply look something like this:
<DockPanel>
<TreeView DockPanel.Dock="Left"
ItemsSource="{Binding Phones}"
SelectedItem="{Binding SelectedPhone}" />
<StackPanel DockPanel.Dock="Bottom" Orientation="Horizontal">
<Button Content="{Binding SaveCommand}" />
<Button Content="{Binding CancelCommand}" />
</StackPanel>
<ContentControl Content="{Binding SelectedPhone} />
</DockPanel>
I would leave the validation in your QuertyPhone model, but would move the data access to the ViewModel. In my opinion, your models should just be dumb data objects. You also only have to register for messaging in a single place (your ViewModel), and if you need to do anything when the SelectedPhone changes, simply do it in the PropertyChanged event.
As a side note, I forget if you can bind a TreeView's SelectedItem or not, but if not you can either use a ListBox styled to look like a TreeView, or in the past I've added IsSelected properties to my TreeView data objects, and bound the TreeViewItem.IsSelected property to that.