Why doesn't the following Image bind to the source properly?
<UserControl x:Class="SlCaliburnConventionTest.Sample"
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">
<Image x:Name="UriProperty" />
</Grid>
</UserControl>
The code behind and the view model:
namespace SlCaliburnConventionTest
{
using System;
using System.Windows.Controls;
public partial class Sample : UserControl
{
public Sample()
{
InitializeComponent();
var viewModel = new SampleViewModel("http://lorempixel.com/300/200/sports/1");
Caliburn.Micro.ViewModelBinder.Bind(viewModel, this, null);
}
}
public class SampleViewModel
{
public SampleViewModel(string url)
{
UriProperty = new Uri(url, UriKind.Absolute);
}
public Uri UriProperty { get; set; }
}
}
I dug into the Caliburn.Micro sources and found that it was not using the TypeDescriptor when applying the conventions. The question is: How do we persuade the Caliburn.Micro to convert Uris into ImageSource?
I use a string as the backing property and the binding works for me:
public class TestViewModel : ViewModelBase
{
public TestViewModel()
{
ImageUrl = "http://cdn.sstatic.net/stackoverflow/img/apple-touch-icon.png";
}
public string ImageUrl { get; set; }
}
<Image Source="{Binding ImageUrl}" />
Image controls demonstrate an interesting property of XAML known as type conversion. For instance, the XAML api for Images look like this:
<Image Source="http://lorempixel.com/100/100/people" />
However, the programming API is like this:
class Image {
ImageSource Source { get; set;}
DependencyProperty SourceProperty // etc.
}
How did a string get turned into an Uri, and then turned into an ImageSource?
The answer lies in TypeConverters.
[TypeConverter(typeof(ImageSourceConverter))]
public class ImageSource {}
When we programmatically create a binding to a Uri, the magic above doesn't take place. And the result is no pictures are shown.
// No picture is shown.
BindingOperations.SetBinding(myImage,
Image.SourceProperty, new Binding("MyUri"));
Similarly we cannot do this:
// compile time error
myImage.Source = new Uri("http://...")
Instead, the proper way is to fetch the type converter from the ImageSource's custom attribute and massage it into an IValueConverter. Here's mine - the main work is performed by this single line public object Convert(...) - everything else is scaffolding:
namespace Caliburn.Micro
{
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
public class ValueTypeConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var result = TypeDescriptor.GetConverter(targetType).ConvertFrom(value);
return result;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
/// <summary>
/// Binding Image.Source to an Uri typically fails.
/// Calling the following during application bootstrap will set this up properly.
/// ConventionManager.ApplyValueConverter = ValueTypeConverter.ApplyValueConverter;
/// </summary>
/// <param name="binding"></param>
/// <param name="bindableProperty"></param>
/// <param name="info"></param>
public static void ApplyValueConverter(Binding binding, DependencyProperty bindableProperty, PropertyInfo info)
{
if (bindableProperty == UIElement.VisibilityProperty && typeof(bool).IsAssignableFrom(info.PropertyType))
binding.Converter = ConventionManager.BooleanToVisibilityConverter;
else if (bindableProperty == Image.SourceProperty && typeof(Uri).IsAssignableFrom(info.PropertyType))
binding.Converter = new ValueTypeConverter();
else
{
foreach (var item in _Conventions)
{
if (bindableProperty == item.Item1 && item.Item2.IsAssignableFrom(info.PropertyType))
binding.Converter = new ValueTypeConverter();
}
}
}
/// <summary>
/// If there is a TypeConverter that can convert a <paramref name="SourceType"/>
/// to the type on <paramref name="bindableProperty"/>, then this has to
/// be manually registered with Caliburn.Micro as Silverlight is unable to
/// extract sufficient TypeConverter information from a dependency property
/// on its own.
/// </summary>
/// <example>
/// ValueTypeConverter.AddTypeConverter<ImageSource>(Image.SourceProperty);
/// </example>
/// <typeparam name="SourceType"></typeparam>
/// <param name="bindableProperty"></param>
public static void AddTypeConverter<SourceType>(DependencyProperty bindableProperty)
{
_Conventions.Add(Tuple.Create<DependencyProperty, Type>(bindableProperty, typeof(SourceType)));
}
private static IList<Tuple<DependencyProperty, Type>> _Conventions = new List<Tuple<DependencyProperty, Type>>();
}
}
Then in the bootstrapper, we wire up the new IValueConverter:
protected override void Configure()
{
// ...
ConventionManager.ApplyValueConverter =
ValueTypeConverter.ApplyValueConverter;
}
Related
I am attempting to make a UserControl to create a CheckBoxList in WPF using MVVM. Also, Entity Framework is being used to deploy the data. Given the following:
WPF (UserControl)
<Grid>
<ListBox Name="ListBox" ItemsSource="{Binding TheList}" >
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding Sport}"
Tag="{Binding SportsId}"
IsChecked="{Binding IsChecked}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
Classes
public class Athlete
{
public int AthleteId { get; set; }
public string Name { get; set; }
public ICollection<Sports> Sports { get; set; }
}
public class Sports {
public int SportsId { get; set; }
public string Sport { get; set; }
}
How can I get the UserControl to load the entire list of the Sports class and then select the ones that the Athlete can play?
I found the solution to my issue. I was able to find it here. It goes like this:
WPF UserControl.xaml
<UserControl x:Class="YourNamespace.CheckBoxList"
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:YourNamespace"
mc:Ignorable="d"
x:Name="ThisCheckBoxList"
d:DesignHeight="450" d:DesignWidth="800">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<StackPanel>
<ItemsControl x:Name="host"
ItemsSource="{Binding ElementName=ThisCheckBoxList, Path=ItemsSource}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:MyCheckBox x:Name="theCheckbox"
DisplayMemberPath="{Binding ElementName=ThisCheckBoxList, Path=DisplayPropertyPath}"
Unchecked="MyCheckBox_Checked"
Checked="MyCheckBox_Checked"
Tag="{Binding Path=.}">
<local:MyCheckBox.IsChecked >
<MultiBinding Mode="OneWay" >
<MultiBinding.Converter>
<local:IsCheckedValueConverter />
</MultiBinding.Converter>
<Binding Path="."></Binding>
<Binding ElementName="ThisCheckBoxList" Path="SelectedItems"></Binding>
<Binding ElementName="ThisCheckBoxList" Path="DisplayPropertyPath"></Binding>
</MultiBinding>
</local:MyCheckBox.IsChecked>
</local:MyCheckBox>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</ScrollViewer>
</UserControl>
WPF UserControl.xaml.cs
using System.Collections;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media;
namespace Eden
{
/// <summary>
/// Interaction logic for CheckBoxList.xaml
/// </summary>
public partial class CheckBoxList : UserControl
{
public CheckBoxList()
{
InitializeComponent();
}
public object ItemsSource
{
get => GetValue(ItemsSourceProperty);
set => SetValue(ItemsSourceProperty, value);
}
// Using a DependencyProperty as the backing store for ItemSource. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register("ItemsSource", typeof(object), typeof(CheckBoxList),
new UIPropertyMetadata(null, (sender, args) => Debug.WriteLine(args)));
public IList SelectedItems
{
get => (IList)GetValue(SelectedItemsProperty);
set => SetValue(SelectedItemsProperty, value);
}
// Using a DependencyProperty as the backing store for SelectedItems. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SelectedItemsProperty =
DependencyProperty.Register("SelectedItems", typeof(IList), typeof(CheckBoxList),
new UIPropertyMetadata(null, SelectedChanged));
/// <summary>
/// This is called when selected property changed.
/// </summary>
/// <param name="obj"></param>
/// <param name="args"></param>
private static void SelectedChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
if (args.NewValue is INotifyCollectionChanged ncc)
{
ncc.CollectionChanged += (sender, e) =>
{
CheckBoxList thiscontrol = (CheckBoxList)obj;
RebindAllCheckbox(thiscontrol.host);
};
}
}
private static void RebindAllCheckbox(DependencyObject de)
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(de); i++)
{
DependencyObject dobj = VisualTreeHelper.GetChild(de, i);
if (dobj is CheckBox cb)
{
var bexpression = BindingOperations.GetMultiBindingExpression(cb, MyCheckBox.IsCheckedProperty);
if (bexpression != null) bexpression.UpdateTarget();
}
RebindAllCheckbox(dobj);
}
}
public string DisplayPropertyPath
{
get => (string)GetValue(DisplayPropertyPathProperty);
set => SetValue(DisplayPropertyPathProperty, value);
}
// Using a DependencyProperty as the backing store for DisplayPropertyPath. This enables animation, styling, binding, etc...
public static readonly DependencyProperty DisplayPropertyPathProperty =
DependencyProperty.Register("DisplayPropertyPath", typeof(string), typeof(CheckBoxList),
new UIPropertyMetadata("", (sender, args) => Debug.WriteLine(args)));
private PropertyInfo mDisplayPropertyPathPropertyInfo;
private void MyCheckBox_Checked(object sender, RoutedEventArgs e)
{
if (SelectedItems == null)
return;
MyCheckBox chb = (MyCheckBox)sender;
object related = chb.Tag;
if (mDisplayPropertyPathPropertyInfo == null)
{
mDisplayPropertyPathPropertyInfo =
related.GetType().GetProperty(
DisplayPropertyPath, BindingFlags.Instance | BindingFlags.Public);
}
object propertyValue;
if (DisplayPropertyPath == ".")
propertyValue = related;
else
propertyValue = mDisplayPropertyPathPropertyInfo.GetValue(related, null);
if (chb.IsChecked == true)
{
if (!SelectedItems.Cast<object>()
.Any(o => propertyValue.Equals(
DisplayPropertyPath == "." ? o : mDisplayPropertyPathPropertyInfo.GetValue(o, null))))
{
SelectedItems.Add(related);
}
}
else
{
object toDeselect = SelectedItems.Cast<object>()
.Where(o => propertyValue.Equals(DisplayPropertyPath == "." ? o : mDisplayPropertyPathPropertyInfo.GetValue(o, null)))
.FirstOrDefault();
if (toDeselect != null)
{
SelectedItems.Remove(toDeselect);
}
}
}
}
public class MyCheckBox : CheckBox
{
public string DisplayMemberPath
{
get => (string)GetValue(DisplayMemberPathProperty);
set => SetValue(DisplayMemberPathProperty, value);
}
// Using a DependencyProperty as the backing store for DisplayMemberPath. This enables animation, styling, binding, etc...
public static readonly DependencyProperty DisplayMemberPathProperty =
DependencyProperty.Register("DisplayMemberPath",
typeof(string),
typeof(MyCheckBox),
new UIPropertyMetadata(string.Empty, (sender, args) =>
{
MyCheckBox item = (MyCheckBox)sender;
Binding contentBinding = new Binding((string)args.NewValue);
item.SetBinding(ContentProperty, contentBinding);
}));
}
}
BaseMultiValueConverter
using System;
using System.Globalization;
using System.Windows.Data;
using System.Windows.Markup;
namespace Eden
{
/// <summary>
/// A base value converter that allows direct XAML usage
/// </summary>
/// <typeparam name="T">The type of this value converter</typeparam>
public abstract class BaseMultiValueConverter<T> : MarkupExtension, IMultiValueConverter
where T : class, new()
{
#region Private Variables
/// <summary>
/// A single static instance of this value converter
/// </summary>
private static T Coverter = null;
#endregion
#region Markup Extension Methods
/// <summary>
/// Provides a static instance of the value converter
/// </summary>
/// <param name="serviceProvider">The service provider</param>
/// <returns></returns>
public override object ProvideValue(IServiceProvider serviceProvider)
{
return Coverter ?? (Coverter = new T());
}
#endregion
#region Value Converter Methods
/// <summary>
/// The method to convert on type to another
/// </summary>
/// <param name="value"></param>
/// <param name="targetType"></param>
/// <param name="parameter"></param>
/// <param name="culture"></param>
/// <returns></returns>
public abstract object Convert(object[] value, Type targetType, object parameter, CultureInfo culture);
/// <summary>
/// The method to convert a value back to it's source type
/// </summary>
/// <param name="value"></param>
/// <param name="targetType"></param>
/// <param name="parameter"></param>
/// <param name="culture"></param>
/// <returns></returns>
public abstract object[] ConvertBack(object value, Type[] targetType, object parameter, CultureInfo culture);
#endregion
}
}
IMultiValueConverter
using EcoDev.Data;
using System;
using System.Collections;
using System.Globalization;
using System.Reflection;
using System.Windows.Data;
namespace Eden
{
/// <summary>
///
/// </summary>
public class IsCheckedValueConverter : BaseMultiValueConverter<IsCheckedValueConverter>
{
private PropertyInfo PropertyInfo { get; set; }
private Type ObjectType { get; set; }
public override object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (values[1] == null) return false; // IF I do not have no value for selected simply return false
if (!(values[2] is string PropertyName)) return false;
if (string.IsNullOrEmpty(PropertyName)) return false;
if (!targetType.IsAssignableFrom(typeof(bool))) throw new NotSupportedException("Can convert only to boolean");
IEnumerable collection = values[1] as IEnumerable;
object value = values[0];
if (value.GetType() != ObjectType)
{
PropertyInfo = value.GetType().GetProperty(PropertyName, BindingFlags.Instance | BindingFlags.Public);
ObjectType = value.GetType();
}
foreach (var obj in collection)
{
if (PropertyName == ".")
{
if (value.Equals(obj)) return true;
}
else
{
if (PropertyInfo.GetValue(value, null).Equals(PropertyInfo.GetValue(obj, null))) return true;
}
}
return false;
}
public override object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
And then all you have to do in whatever window/page you want to use it in is use this code:
<local:CheckBoxList Height="Auto"
SelectedItems="{Binding SelectedItems}"
ItemsSource="{Binding ItemsSource}"
DisplayPropertyPath="Text"/>
The question is very broad and vague but I try to explain the best I can. You may need to read the whole thing at least twice. And also read the external link to the end of it or at least carefully read the codes in it.
First look at the final solution:
public class AthleteVM : DependencyObject
{
public int AthleteId { get; set; }
public string Name { get; set; }
private ObservableCollection<SportSelectionVM> _sports = new ObservableCollection<SportSelectionVM>();
public ObservableCollection<SportSelectionVM> Sports { get { return _sports; } }
}
public class SportSelectionVM : DependencyObject
{
public int SportsId { get; set; }
public string Name { get; set; }
private Model.Sport _model;
public SportSelectionVM(Model.Sport model, bool isSelected)
{
_model = model;
SportsId = model.Id;
Name = model.Name;
IsSelected = isSelected;
}
/// <summary>
/// Gets or Sets IsSelected Dependency Property
/// </summary>
public bool IsSelected
{
get { return (bool)GetValue(IsSelectedProperty); }
set { SetValue(IsSelectedProperty, value); }
}
public static readonly DependencyProperty IsSelectedProperty =
DependencyProperty.Register("IsSelected", typeof(bool), typeof(AthleteVM), new PropertyMetadata(false, (d, e) =>
{
// PropertyChangedCallback
var vm = d as SportSelectionVM;
var val = (bool)e.NewValue;
AthleteDataService.UpdateModel(vm._model, val);//database changes here
}));
}
XAML:
<ListBox Name="ListBox" ItemsSource="{Binding Sports}" >
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding Name}"
Tag="{Binding SportsId}"
IsChecked="{Binding IsSelected}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
DataContext of this view is an instance of AthleteVM. add all sports to Sports in AthleteVM and set IsSelected on those necessary.
see the constructor: public SportSelectionVM(Model.Sport model, bool isSelected)
similar strategy should be used to create an AthleteVM or to populate AthleteVM list in its parent.
EF and UOW:
As we know here's the idea behind MVVM:
[Model] <--- [VM] <--TwoWay Binding--> [View]
When EF is added to this pattern, it is usually recommended to follow UOW pattern as well.
Typically the UOW (UnitOfWork) is an object which is in charge of one database transaction (I don't mean SQLTransaction) and it is recommended to always create a UOW inside a using statement so that it would be disposed afterwards. Using this approach, you should expect to run into this question: how different UOWs interact with each others. which answer is: they don't.
Each UOW creates a lazy copy of the database and starts to modify it until you tell it to discard or save. If another UOW is created in the middle of this process, it doesn't contain any of the changes made to previous UOWs, unless the previous UOW is saved.
So you don't have to worry about Model and instead, you'll focus on the DataService to have something like this.
Model<->VM
Considering all this information, ViewModel simply uses an instance of a DataService to fetch data from database and puts them in bindable properties and observable collections to maintain a TwoWay Binding.
But VM and Model do not have a TwoWay relationship, meaning that any change to ViewModel should be reflected on Model and then be saved in database manually.
My favourite solution is to take full advantage of PropertyChangedCallback feature of DependencyProperty to tell the DataService to reflect the change:
public int MyProperty
{
get { return (int)GetValue(MyPropertyProperty); }
set { SetValue(MyPropertyProperty, value); }
}
public static readonly DependencyProperty MyPropertyProperty =
DependencyProperty.Register("MyProperty", typeof(int), typeof(MyViewModel),
new PropertyMetadata(0, (d,e)=>
{
var vm = d as MyViewModel;
var val = (int)e.NewValue;//check conditions here
vm._model.MyProperty = val;//update model
vm._dataService.Update(vm._model);//update database
}));
in above sample, class MyViewModel has an instance of _model and _dataService.
Here is my issue, I created a UserControl as follows:
XAML:
<UserControl x:Class="ProcessVisualizationBar.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d" xmlns:lb="clr-namespace:ProcessVisualizationBar"
Name="ProcessVisualizationBar">
<Border BorderBrush="Silver" BorderThickness="1,1,1,1" Margin="0,5,5,5" CornerRadius="5" Padding="2">
<ListBox Name="ProcessVisualizationRibbon" Grid.Column="1" Height="40" ItemsSource="{Binding ElementName=ProcessVisualizationBar, Path=ItemsSource}"/>
</Border>
</UserControl>
Code Behind(C#):
using System.Windows;
using System.Windows.Controls;
namespace ProcessVisualizationBar
{
public partial class UserControl1 : UserControl
{
public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register("ItemsSource", typeof(System.Collections.IEnumerable), typeof(UserControl));
public System.Collections.IEnumerable ItemsSource
{
get { return ProcessVisualizationRibbon.ItemsSource; }
set { ProcessVisualizationRibbon.ItemsSource = value; }
}
public UserControl1()
{
InitializeComponent();
}
}
}
I build my Usercontrol and add the .dll to the reference of another project. I add the reference at the top of my XAML as such:
xmlns:uc="clr-namespace:ProcessVisualizationBar;assembly=ProcessVisualizationBar"
Then I go to use the control.
<uc:UserControl1 Grid.Row="2" x:Name="ProcessVisualizationContent" />
It finds the control okay, but when I try and find the ItemsSource Property I added to it, I'm not finding it. I'm not sure what I missed, and I'm not sure what debug tools are really available to figure this out.
Anyone have some experience with this that can share their wisdom?
What is the actual data being passed? That is what you should be creating and not a pass through situation which you are attempting.
Create a dependency property targetting the actual data to be passed with a property changed handler. On the change event, then call internal code to bind it to the ProcessVisualazation ItemsSource. That way you can debug when the data comes through by placing a breakpoint in the event.
Here is an example where the consumer will see StringData in the Xaml and needs to pas a list of strings into the custom control:
#region public List<string> StringData
/// <summary>
/// This data is to be bound to the ribbon control
/// </summary>
public List<string> StringData
{
get { return GetValue( StringDataProperty ) as List<string>; }
set { SetValue( StringDataProperty, value ); }
}
/// <summary>
/// Identifies the StringData dependency property.
/// </summary>
public static readonly System.Windows.DependencyProperty StringDataProperty =
System.Windows.DependencyProperty.Register(
"StringData",
typeof( List<string> ),
typeof( UserControl ),
new System.Windows.PropertyMetadata( null, OnStringDataPropertyChanged ) );
/// <summary>
/// StringDataProperty property changed handler.
/// </summary>
/// <param name="d">DASTreeBinder that changed its StringData.</param>
/// <param name="e">Event arguments.</param>
private static void OnStringDataPropertyChanged( System.Windows.DependencyObject d, System.Windows.DependencyPropertyChangedEventArgs e )
{
UserControl source = d as UserControl;
List<string> value = e.NewValue as List<string>;
BindDataToRibbon( value );
}
#endregion public List<string> StringData
Now just create a BindDataToRibbon method which will do the dirty work. Note that I use Jeff Wilcox's Silverlight dependency snippets in Visual Studio to generate the above dependency. I have used it for WPF and Silverlight projects.
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.
With the following code, although Text property is bound to a DateTime source property, I noticed WPF seems to automatically convert the text to a DateTime, without me needing to write a ValueConverter. Can someone please shed some light on how this is done
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:WpfApplication1="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525"
>
<StackPanel>
<DatePicker Height="25" Name="datePicker1" Width="213" Text="{Binding Path=DueDate,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>
</Window>
public class P
{
private DateTime? dueDate = DateTime.Now;
public DateTime? DueDate
{
get { return dueDate; }
set
{
dueDate = value;
}
}
}
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
P p = new P();
this.DataContext = p;
}
}
It is using the DateTimeTypeConverter from the Base Class Library (EDIT: Well, it could have used a TypeConverter however it appears that from #DeviantSeev's answer that they did not).
There 'default' converters you are talking about are actually TypeConverters (MSDN) and they have been a part of the .NET Framework since v2.0 and they are used through-out the Base Class Libraries. Another example of TypeConverters in WPF is the ThicknessTypeConverter for Padding, Margin, and BorderThickness properties. It converts a comma-delimited string to a Thickness object.
There are plenty of articles available if you want to understand them further.
There are two parts to using a TypeConverter - implementation of the class and then marking up your properties/types with TypeConverterAttribute.
For example, I recently had a custom control that required a char[] that I wanted to set from Xaml like so:
<AutoCompleteTextBox MultiInputDelimiters=",;. " />
Usage
[TypeConverter(typeof(CharArrayTypeConverter))]
public char[] MultiInputDelimiters
{
get { return (char[])GetValue(MultiInputDelimitersProperty); }
set { SetValue(MultiInputDelimitersProperty, value); }
}
Implementation
public class CharArrayTypeConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return (Type.GetTypeCode(sourceType) == TypeCode.String);
}
public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
{
if (value is string)
return ((string)value).ToCharArray();
return value;
}
}
When to use a TypeConverter?
You can only use TypeDescriptors if you are writing a custom control as you need to be able to mark-up the property with the TypeDescriptorAttribute. Also I would only use TypeConverter if the conversion is rather a straight-forward - as in the example above where I have a string and want a char[] - or if there are multiple possible formats that I want to convert from.
You write IValueConverter when you want more flexibility on how the value to converted by driving it by data or a passing a parameter. For example, a very common action in WPF is converting a bool to Visibility; there are three possible outputs from such a conversion (Visible, Hidden, Collapsed) and with only two inputs (true, false) it difficult to decide this in a TypeConverter.
In my applications, to achieve this two inputs to three output problem I have written a single BoolToVisibilityConverter with a TrueValue and FalseValue properties and then I instance it three times in my global ResourceDictionary. I'll post the code sample tomorrow morning, I don't it in front of me right now..
[ValueConversion(typeof(bool), typeof(Visibility))]
public class BooleanToVisibilityConverter : IValueConverter
{
public Visibility FalseCondition { get; set; }
public Visibility TrueCondition { get; set; }
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return ((bool)value) ? TrueCondition : FalseCondition;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if ((bool)value)
return TrueCondition;
return FalseCondition;
}
}
<converters:BooleanToVisibilityConverter x:Key="BoolToVisibilityConverter" FalseCondition="Collapsed" TrueCondition="Visible"/>
<converters:BooleanToVisibilityConverter x:Key="BoolToVisibilityCollapsedConverter" FalseCondition="Visible" TrueCondition="Collapsed"/>
<converters:BooleanToVisibilityConverter x:Key="BoolToVisibilityHiddenConverter" FalseCondition="Visible" TrueCondition="Hidden"/>
<converters:BooleanToVisibilityConverter x:Key="BoolToVisibilityHiddenWhenFalseConverter" FalseCondition="Hidden" TrueCondition="Visible"/>
The DatePicker is a custom control that was initially part of the WPF Toolkit before being added as a standard control in .NET 4.
I just went to the source code repository for the control to find you the exact source code which is responsible for the conversion of the text to date:
#region Text
/// <summary>
/// Gets or sets the text that is displayed by the DatePicker.
/// </summary>
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
/// <summary>
/// Identifies the Text dependency property.
/// </summary>
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register(
"Text",
typeof(string),
typeof(DatePicker),
new FrameworkPropertyMetadata(string.Empty, OnTextChanged, OnCoerceText));
/// <summary>
/// TextProperty property changed handler.
/// </summary>
/// <param name="d">DatePicker that changed its Text.</param>
/// <param name="e">DependencyPropertyChangedEventArgs.</param>
private static void OnTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
DatePicker dp = d as DatePicker;
Debug.Assert(dp != null);
if (!dp.IsHandlerSuspended(DatePicker.TextProperty))
{
string newValue = e.NewValue as string;
if (newValue != null)
{
if (dp._textBox != null)
{
dp._textBox.Text = newValue;
}
else
{
dp._defaultText = newValue;
}
dp.SetSelectedDate();
}
else
{
dp.SetValueNoCallback(DatePicker.SelectedDateProperty, null);
}
}
}
private static object OnCoerceText(DependencyObject dObject, object baseValue)
{
DatePicker dp = (DatePicker)dObject;
if (dp._shouldCoerceText)
{
dp._shouldCoerceText = false;
return dp._coercedTextValue;
}
return baseValue;
}
/// <summary>
/// Sets the local Text property without breaking bindings
/// </summary>
/// <param name="value"></param>
private void SetTextInternal(string value)
{
if (BindingOperations.GetBindingExpressionBase(this, DatePicker.TextProperty) != null)
{
Text = value;
}
else
{
_shouldCoerceText = true;
_coercedTextValue = value;
CoerceValue(TextProperty);
}
}
#endregion Text
In most cases I believe WPF is calling ToString() for you however if you look at the code for the date picker the important line is
(string)GetValue(TextProperty)
notice it casts the value you assigned to the "Text" property to a string? The whole point is there is no default converter in the more traditional sense of BooleanToVisibilityConverter or something like that.
I'm trying to bind a DocumentViewer to a document via the ViewModel and am not succeeding at all.
Here is my view model code...
private DocumentViewer documentViewer1 = new DocumentViewer();
public DocumentViewerVM()
{
string fileName = System.IO.Path.Combine(System.IO.Path.GetTempPath(), "Here an xps document.xps");
XpsDocument document = new XpsDocument(fileName, FileAccess.Read);
documentViewer1.Document = document.GetFixedDocumentSequence();
document.Close();
}
public DocumentViewer DocumentViewer1
{
get
{ return documentViewer1; }
set
{
documentViewer1 = value;
OnPropertyChanged("DocumentViewer1");
}
}
here is the xaml in the view...
<UserControl x:Class="DemoApp.View.DocumentViewerView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
>
<Grid>
<DocumentViewer Name="DocumentViewer1" Document="{Binding Path=DocumentViewer1, UpdateSourceTrigger=PropertyChanged}" ></DocumentViewer>
</Grid>
</UserControl>
the code behind for the view contains no code other than 'InitializeComponent()'
What I do find strange is that if I place the document generation code from the view model constructor into the view constructor the document is displayed correctly, this leads me to think it is a binding issue, but where or how I know not.
You are binding the Document property of the DocumentViewer to a property called DocumentViewer1 which is itself a DocumentViewer. The Document property expects an instance of a type that implements IDocumentPaginatorSource, such as a FixedDocument.
If you want to keep your view models pristine and avoid including PresentationCore.dll in your view model library, then use a WPF IValueConverter such as the following.
namespace Oceanside.Desktop.Wpf.Dialogs.Converters
{
using System;
using System.Globalization;
using System.IO;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Xps.Packaging;
////////////////////////////////////////////////////////////////////////////////////////////////////
/// <inheritdoc />
/// <summary>
/// Our view models contain string paths to all XPS documents that we want to show. However,
/// the DocumentViewer.Document property must be of type IDocumentPaginatorSource which we do
/// not want to include in our view model because it will tie our view models to the
/// PresentationCore.dll. To assure all view logic and libraries are kept separate from our
/// view model, this converter to take a string path and convert it to a
/// FixedDocumentSequence which implements the IDocumentPaginatorSource interface.
/// </summary>
////////////////////////////////////////////////////////////////////////////////////////////////////
[ValueConversion(typeof(string), typeof(IDocumentPaginatorSource))]
public sealed class DocumentPaginatorSourceConverter : IValueConverter
{
////////////////////////////////////////////////////////////////////////////////////////////////////
/// <inheritdoc />
////////////////////////////////////////////////////////////////////////////////////////////////////
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
if (!(value is string xpsFilePath)) return null;
var document = new XpsDocument(xpsFilePath, FileAccess.Read);
var source = document.GetFixedDocumentSequence();
document.Close();
return source;
}
////////////////////////////////////////////////////////////////////////////////////////////////////
/// <inheritdoc />
/// <summary>This function is not supported and will throw an exception if used.</summary>
////////////////////////////////////////////////////////////////////////////////////////////////////
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
//We have no use to convert from IDocumentPaginatorSource to a string path.
throw new NotSupportedException("Unable to convert an IDocumentPaginatorSource to a string path.");
}
}
}
The XAML below shows how to use the above converter. This example is a data template that has a view model of Type MessageBoxShowXpsDoc which has a simple string property called DocumentPath. This is passed to the converter to obtain the IDocumentPaginatorSource.
<!-- For showing xps/flow docs such as customer receipts -->
<DataTemplate
DataType="{x:Type dialogVms:MessageBoxShowXpsDoc}"
xmlns:converters="clr-namespace:Oceanside.Desktop.Wpf.Dialogs.Converters">
<DataTemplate.Resources>
<converters:DocumentPaginatorSourceConverter x:Key="DocPagConverter" />
</DataTemplate.Resources>
<DocumentViewer Document="{Binding DocumentPath,
Converter={StaticResource DocPagConverter}}" />
</DataTemplate>
Although including the full view model is outside the scope of the OP, this is an example of how I set that string path which is passed from the view model to the converter.
var viewModel = MessageBoxShowXpsDoc {DocumentPath = #"TestData\sample.xps"};
As explained already by devdigital (above), a public property of type
IDocumentPaginatorSource is needed.
Something like this perhaps:
private IDocumentPaginatorSource _fixedDocumentSequence;
public IDocumentPaginatorSource FixedDocumentSequence
{
get { return _fixedDocumentSequence; }
set
{
if (_fixedDocumentSequence == value) return;
_fixedDocumentSequence = value;
OnPropertyChanged("FixedDocumentSequence");
}
}
And in your xaml just bind this to the DocumentViewer Document property:
<Grid>
<DocumentViewer
Name="DocViewer"
Document="{Binding FixedDocumentSequence}"/>
</Grid>
For people who might still have no clue how to get it done. Here is an example:
The View:
<Grid>
<DocumentViewer HorizontalAlignment="Center"
Margin="0,20,0,0"
Document="{Binding Manual}"
VerticalAlignment="Top"
Height="508" Width="766" />
</Grid>
The ViewModel:
public OperationManualViewModel()
{
var _xpsPackage = new XpsDocument(#"C:\Users\me\Desktop\EngManual.xps", FileAccess.Read);
_manual = _xpsPackage.GetFixedDocumentSequence();
}
private IDocumentPaginatorSource _manual;
public IDocumentPaginatorSource Manual
{
get { return _manual; }
set { _manual = value; NotifyOfPropertyChange(() => Manual); }
}