I have dialogbox with Content control with templates:
<ContentControl Content="{Binding Model,UpdateSourceTrigger=PropertyChanged}" ContentTemplateSelector="{StaticResource TemplateSelector}"/>
and property change event at dialogbox context:
dialogContext.Model.PropertyChanged += (s, e) => Change(s,e, context);
private void Change(object s, PrropertyChangeEventArgs e, Context context)
{
...
context.Mode = new Example()
{
...
}
model.PropertyChanged += (sender, eventArgs) =>
ModelChange(sender, eventArgs, context);
context.Model = model;
}
I want to change some properties at model, that determine which custom template will be displayed.
To reload new template and invoke temlate selector should I create new model and
add property change event to this. Is is ok, or is it another way to do this.
Update
The below implementation doesn't work because it turns out that the template selector is only reinvoked if the actual value of ContentControl.Content changes. If you've still got the same instance of Model, raising PropertyChanged will have no effect. I even tried overriding ModelClass.Equals() and ModelClass.GetHashCode(). Neither was called. Maybe the Binding is calling Object.ReferenceEquals().
But I did find three ways to do this. All have been tested, now that I've learned my lesson.
If you're going to this much trouble to get a template selector to work, best to look for some other approach where you're not fighting the framework.
You could instead use style triggers to swap templates:
<ContentControl
Content="{Binding Model}"
>
<ContentControl.Style>
<Style TargetType="ContentControl">
<Style.Triggers>
<DataTrigger Binding="{Binding Model.Foo}" Value="foo">
<Setter
Property="ContentTemplate"
Value="{StaticResource Foo}"
/>
</DataTrigger>
<DataTrigger Binding="{Binding Model.Foo}" Value="bar">
<Setter
Property="ContentTemplate"
Value="{StaticResource Bar}"
/>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
...but the logic in your template selector may be quite a bit more complicated than that, in which case it may not be feasible.
Here's another. You don't need a template selector to select a template. A converter can return a DataTemplate too, and if you use a multi-binding converter, you can give it whatever it needs to look up a DataTemplate in the resources:
<ContentControl
Content="{Binding Model}"
>
<ContentControl.ContentTemplate>
<MultiBinding
Converter="{StaticResource ContentTemplateConverter}"
>
<!--
We must bind to Model.Foo so the binding updates when that changes,
but we could also bind to Model as well if the converter wants to
look at other properties besides Foo.
-->
<Binding Path="Model.Foo" />
<!-- The ContentControl itself will be used for FindResource() -->
<Binding RelativeSource="{RelativeSource Self}" />
</MultiBinding>
</ContentControl.ContentTemplate>
</ContentControl>
C#
public class ContentTemplateConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
var ctl = values[1] as FrameworkElement;
switch ($"{values[0]}")
{
case "foo":
return ctl.FindResource("Foo") as DataTemplate;
case "bar":
return ctl.FindResource("Bar") as DataTemplate;
}
return null;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
One last possibility, and in my opinion the least, is to use the template selector, but make it work by actually replacing the value of Model every time one of its properties changes. Rewrite ModelClass so it can easily be cloned:
public ModelClass() {}
public ModelClass(ModelClass cloneMe) {
this.Foo = cloneMe.Foo;
this.Bar = cloneMe.Bar;
}
...and keep _model_PropertyChanged from my original answer, but change the guts so instead of merely raising PropertyChanged, it replaces the actual value of Model (which will of course still raise PropertyChanged, as a side effect):
private void _model_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(ModelClass.Foo))
{
Model = new ModelClass(Model);
}
}
I've tested that and while it's alarmingly goofy, it does work.
Instead of cloning ModelClass, you could use a "reference" class for the parent's Model property:
public class ModelClassRef {
public ModelClassRef(ModelClass mc) { ... }
public ModelClassRef { get; private set; }
}
But it's still wicked goofy. The viewmodel shouldn't "know" the view even exists, but here you are rewriting a chunk of it in a bizarre way just to work around a peculiarity in the implementation of a particular control. View workarounds belong in the view.
So when this.Model.Foo changes, you want to change the template? I would expect this to do the job:
#region Model Property
private ModelClass _model = null;
public ModelClass Model
{
get { return _model; }
set
{
if (value != _model)
{
if (_model != null)
{
_model.PropertyChanged -= _model_PropertyChanged;
}
_model = value;
if (_model != null)
{
_model.PropertyChanged += _model_PropertyChanged;
}
OnPropertyChanged();
}
}
}
private void _model_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
// If Model.Foo changed, announce that Model changed. Any binding using
// the Model property as its source will update, and that will cause
// the template selector to be re-invoked.
if (e.PropertyName == nameof(ModelClass.Foo))
{
OnPropertyChanged(nameof(Model));
}
}
This is defined in your viewmodel base class. Maybe you've already got essentially the same method and it's called something else; if so, use that one of course.
protected void OnPropertyChanged([CallerMemberName] String propName = null)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
By the way, get rid of UpdateSourceTrigger=PropertyChanged. ContentControl will never create a new value for its Content property and pass that back to your viewmodel through the binding. Can't, won't, and you wouldn't want it to. So you don't need to tell it exactly when to perform a task it's not capable of performing.
Related
I have an ItemsControl bound to an ItemsSource. Each item can have one of several DataTemplates assigned depending on the value of various properties on the item. These properties can change at runtime, and the DataTemplates need to be swapped out individually. In WPF I was able to do so with the following (partial simplified xaml):
<ItemsControl
ItemsSource="{Binding Items}">
<ItemsControl.ItemContainerStyle>
<Style TargetType="{x:Type ContentPresenter}">
<Setter Property="ContentTemplate">
<Setter.Value>
<MultiBinding Converter="{StaticResource RowTemplateConverter}">
<Binding Path="(local:Row.Sum)" />
<Binding Path="(local:Row.Avg)" />
<Binding Path="(local:Row.ShowFlagA)" />
<Binding Path="(local:Row.ShowFlagB)" />
</MultiBinding>
</Setter.Value>
</Setter>
</Style>
</ItemsControl.ItemContainerStyle>
I've run into several issues trying to move this to UWP:
MultiBinding is not supported
To compensate for the above, I tried consolidating the converter logic into a single string property of the Row but the DataTemplate doesn't appear to be assigned. Also the binding syntax I used gives runtime XAML errors, not sure why.
<ItemsControl
ItemsSource="{Binding Items}" >
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="ContentTemplate">
<Setter.Value>
<Binding Path="RowTemplate" Converter="{StaticResource RowTemplateConverter}"/>
</Setter.Value>
</Setter>
</Style>
</ItemsControl.ItemContainerStyle>
DataTemplateSelector and ItemContainerStyleSelectors won't work because they're only evaluated once, and I need the templates updated on various property changes
I've seen some answers here that say to null the above Selectors and re-assign them. This is the closest I've been able to get to my desired behavior, but the performance is poor with dozens of items, and fast changing properties, so I'm unable to use this.
You can write an attached behavior to accomplish this. Alternatively extend e.g. ItemsControl (or a derived type).
The key is to reassign the item container's content in order to invoke the DataTemplateSelector again.
The attacehed property will reset the content to trigger the DataTemplateSelector. Your view model will track the changes of the data items that require to re-evaluate the actual DataTemplate and finally trigger the attached property. This is done by simply assigning the changed item to a view model property that binds to the attached behavior.
First create a template selector by extending DataTemplateSelector:
public class DataItemTemplateSelector : DataTemplateSelector
{
public DataTemplate ActivatedTemplate { get; set; }
public DataTemplate DeactivatedTemplate { get; set; }
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
{
switch (item)
{
case DataItem dataItem when dataItem.IsActivated: return this.ActivatedTemplate;
default: return this.DeactivatedTemplate;
}
}
}
Implement the attached behavior that modifies the container of the changed item:
public class TemplateSelector : DependencyObject
{
public static object GetChangedItem(DependencyObject obj)
{
return (object)obj.GetValue(ChangedItemProperty);
}
public static void SetChangedItem(DependencyObject obj, object value)
{
obj.SetValue(ChangedItemProperty, value);
}
public static readonly DependencyProperty ChangedItemProperty =
DependencyProperty.RegisterAttached("ChangedItem", typeof(object), typeof(TemplateSelector), new PropertyMetadata(default(object), OnChangedItemChanged));
private static void OnChangedItemChanged(DependencyObject attachingElement, DependencyPropertyChangedEventArgs e)
{
if (!(attachingElement is ItemsControl itemsControl))
{
throw new ArgumentException($"Attaching element must be of type '{nameof(ItemsControl)}'");
}
var container = (itemsControl.ItemContainerGenerator.ContainerFromItem(e.NewValue) as ContentControl);
var containerContent = container.Content;
container.Content = null;
container.Content = containerContent; // Trigger the DataTemplateSelector
}
}
Apply the attached property and bind it to your view model. Also assign the template selector:
<Page>
<Page.Resources>
<local:DataItemTemplateSelector x:Key="TemplateSelector">
<local:DataItemTemplateSelector.ActivatedTemplate>
<DataTemplate x:DataType="local:DataItem">
<TextBlock Text="{Binding Text}" Foreground="Red" />
</DataTemplate>
</local:DataItemTemplateSelector.ActivatedTemplate>
<local:DataItemTemplateSelector.DeactivatedTemplate>
<DataTemplate x:DataType="local:DataItem">
<TextBlock Text="{Binding Text}" Foreground="Black" />
</DataTemplate>
</local:DataItemTemplateSelector.DeactivatedTemplate>
</local:DataItemTemplateSelector>
</Page.Resources>
<Grid>
<ListBox ItemsSource="{x:Bind MainViewModel.DataItems}"
local:TemplateSelector.ChangedItem="{x:Bind MainViewModel.UpdatedItem, Mode=OneWay}"
ItemTemplateSelector="{StaticResource TemplateSelector}" />
</Grid>
</Page>
Finally let the view model track the relevant property changes and set the changed property e.g. to a UpdatedItem property which binds to the attached behavior (see above):
public class MainViewModel : ViewModel, INotifyPropertyChanged
{
public MainViewModel()
{
DataItems = new ObservableCollection<DataItem>();
for (int index = 0; index < 10; index++)
{
DataItem newItem = new DataItem();
// Listen to property changes that are relevant
// for the selection of the DataTemplate
newItem.Activated += OnItemActivated;
this.DataItems.Add(newItem);
}
}
// Trigger the attached property by setting the property that binds to the behavior
private void OnItemActivated(object sender, EventArgs e) => this.UpdatedItem = sender as DataItem
public ObservableCollection<DataItem> DataItems { get; }
private DataItem updatedItem;
public DataItem UpdatedItem
{
get => this.updatedItem;
set
{
this.updatedItem = value;
OnPropertyChanged();
}
}
}
this only updates the container of the item that you select in the view model.
Yep, The DataItemTemplateSelector works when preparing items. it will not response the item's property change even if it has implement INotifyPropertyChanged interface, the better way is use IValueConverter to update the uielement base on the specific property.
For example
public class ImageConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
object img = null;
switch (value.ToString())
{
case "horizontal":
img = new BitmapImage(new Uri("ms-appx:///Assets/holder1.png"));
break;
case "vertical":
img = new BitmapImage(new Uri("ms-appx:///Assets/holder2.png"));
break;
default:
break;
}
return img;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
For please refer IValueConverter document.
I'm trying to display a cell containing a 'NULL' string instead of a blank cell when the corresponding source value is null. I'm using a DataGrid bound to a DataTable and AutoGenerateColumns="True".
Previously I managed to do it in the code-behind through the AutoGeneratedColumns event, but now I've switched to MVVM design and I wanted to avoid this.
foreach (var column in dgwCustomTableSelected.Columns)
{
if (column is DataGridTextColumn)
{
((DataGridTextColumn)column).Binding =
new Binding() {
Converter = new NullValueConverter((string)column.Header)
};
}
}
I was wondering whether there's a way to associate a Converter to all the datagrid columns or any other feasible solution to this.
Thanks in advance
I was wondering whether there's a way to associate a Converter to all the datagrid columns or any other feasible solution to this.
The "feasible solution" would be to handle the AutoGeneratingColumn event in the view. Or define all columns explicitly in the XAML markup of the same view.
Neither approach breaks the MVVM pattern since this is view-related functionality and MVVM is not about eliminating view-related code from the views. It is mainly about separation of concerns and doing view-related things programmatically instead of doing it in the XAML markup is perfectly fine.
I would try to do it in the ViewModel. Lets say your ViewModel class would look something like this (I left INotofyPropertyChanged out for simplicity):
public class ViewModel
{
private ModelClass model = new ModelClass();
public string Name
{
get
{
return model.Name;
}
set
{
model.name = value;
}
}
}
You could refactor it to something like this:
public class ViewModel
{
private ModelClass model = new ModelClass();
public string Name
{
get
{
if(model.Name == null)
{
return "NULL";
}
return model.Name;
}
set
{
model.name = value;
}
}
}
You could also try Custom Markup Extension that will allow you to include Converter inside of the Binding. For that to work you would also have to create DataTemplates for different types of data but overall gain, namely handling the data types, would outweigh the amount of coding. Here is an example of said Markup Extension from searchwindevelopment
class NumberToBrushConverter : MarkupExtension, IValueConverter
{
private static NumberToBrushConverter _converter = null;
public override object ProvideValue(IServiceProvider serviceProvider)
{
// determine if we have an instance of converter
// return converter to client
return _converter ?? (_converter = new NumberToBrushConverter());
}
public object Convert(object value, Type targetType,
object parameter,CultureInfo culture)
{
return new SolidColorBrush(Colors.Orange);
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Then in xaml you would use it like this:
<Window x:Class="ValueConverterTips.CustomMarkupTip"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:converters='clr-namespace:ValueConverterTips.Converters'
Title="CustomMarkupTip" >
<!-- no longer need the resources section -->
<Grid>
<Ellipse Fill='{Binding SomeIntData,
Converter={converters:NumberToBrushConverter}}'
Width='10' Height='10' />
</Grid>
</Window>
You would have to modify this to suit your scenario.
EDIT
This is how you would use this with AutoGenerateColumns=true:
<Style TargetType="DataGridColumnHeader">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Button Content="Ok"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<DataGrid AutGenerateColumns="true" ItemsSource={Binding xxx} etc...>
</DataGrid>
Ok,
I have seen a few similar questions but have not been able to figure out this problem for the past couple days. I have two Comboboxes and I want each one to hide the selected element in the other one. For example, if I select a value in ComboBox 1 that selected item should be removed as an option in ComboBox 2.
I thought about using a command but ComboBoxes don't have commands. I have pasted below the comboboxes' XAML and ViewModel code. I would appreciate any help with this. I know the code below is wrong but I think that the logic for this should be in the setters of the bounded to ItemSource.
<ComboBox Margin="0,7,0,0"
Name="ComboBoxA"
HorizontalAlignment="Stretch"
Header="{Binding AccountHeader}"
ItemTemplate="{StaticResource ComboBoxTemplate}"
ItemsSource="{Binding ChargedAccounts,
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}"
SelectedItem="{Binding SelectedAccount,
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}" />
<ComboBox x:Uid="TargetAccountTextBox"
Name="ComboBoxB"
Margin="0,7,0,0"
HorizontalAlignment="Stretch"
Header="target account"
ItemTemplate="{StaticResource ComboBoxTemplate}"
ItemsSource="{Binding TargetAccounts,
Mode=TwoWay,
namespace MoneyFox.Shared.ViewModels
{
[ImplementPropertyChanged]
public class ModifyPaymentViewModel : BaseViewModel, IDisposable
{
private readonly IDefaultManager defaultManager;
private readonly IDialogService dialogService;
private readonly IPaymentManager paymentManager;
//this token ensures that we will be notified when a message is sent.
private readonly MvxSubscriptionToken token;
private readonly IUnitOfWork unitOfWork;
// This has to be static in order to keep the value even if you leave the page to select a category.
private double amount;
private Payment selectedPayment;
public ModifyPaymentViewModel(IUnitOfWork unitOfWork,
IDialogService dialogService,
IPaymentManager paymentManager,
IDefaultManager defaultManager)
{
this.unitOfWork = unitOfWork;
this.dialogService = dialogService;
this.paymentManager = paymentManager;
this.defaultManager = defaultManager;
TargetAccounts = unitOfWork.AccountRepository.Data;
ChargedAccounts = unitOfWork.AccountRepository.Data;
token = MessageHub.Subscribe<CategorySelectedMessage>(ReceiveMessage);
}
ObservableCollection<Account> _SelectedAccount;
ObservableCollection<Account> SelectedAccount
{
get
{
return _SelectedAccount;
}
set
{
_SelectedAccount = value;
for(int i = 0; i < ChargedAccounts.Count; i++)
{
if(ChargedAccounts[i].ToString() == _SelectedAccount.ToString())
{
ChargedAccounts.Remove(ChargedAccounts[i]);
}
}
}
}
ObservableCollection<Account> _TargetAccount;
ObservableCollection<Account> Targetccount
{
get
{
return _SelectedAccount;
}
set
{
_SelectedAccount = value;
for (int i = 0; i < TargetAccounts.Count; i++)
{
if (TargetAccounts[i].ToString() == _SelectedAccount.ToString())
{
TargetAccounts.Remove(ChargedAccounts[i]);
}
}
}
}
While I do agree with a lot of the points in the answer provided by Ed, there is a simpler way to do this without DataTriggers or Converters. There is already a filterable CollectionViewSource in the framework that is your friend (Scott Hanselman loves it)
I would bind ComboBoxA to your regular ChargedAccounts property, but I would modify ComboBoxB to:
bind to a property in the code behind of the View that returns a ICollectionView
in a SelectionChanged event handler for ComboBoxA (also in the code behind of the view) I would adjust the filter for the ICollectionView to exclude the currently selected item
Roughly, this can be done in just a couple of lines:
public ICollectionView FilteredData { get; set; }
private void ComboBoxA_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
var z = new CollectionViewSource {Source = ViewModel.ChargedAccounts.Where(p => p != ViewModel.SelectedAccount) };
FilteredData = z.View;
}
Of course this assumes you've done the right thing by having a ViewModel property in the code behind of your view preferably exposed as an interface, and that the ChargedAccounts and SelectedAccount properties are available via that interface.
You could also cobble these couple of lines together in your viewmodel and trigger it via a property change on SelectedAccount - I just have the opinion that a filter operation in response to a UI action should go in the code behind of the UI, but that decision is really up to you.
Give the comboboxes an ItemContainerStyle (TargetType="ComboBoxItem") with a data trigger. For ComboBoxA, that'll look like this:
<ComboBox
...
x:Name="ComboBoxA"
...
>
<ComboBox.ItemContainerStyle>
<Style TargetType="ComboBoxItem">
<Style.Triggers>
<DataTrigger Value="True">
<DataTrigger.Binding>
<MultiBinding
Converter="{local:ObjectEquals}"
>
<Binding
Path="SelectedItem"
ElementName="ComboBoxB" />
<!-- Binding with no properties just binds to the DataContext -->
<Binding />
</MultiBinding>
</DataTrigger.Binding>
<Setter
Property="Visibility"
Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</ComboBox.ItemContainerStyle>
</ComboBox>
ComboBoxB gets the same deal, but ElementName="ComboBoxA" in the SelectedItem binding.
And we'll need to write that multi-value converter. It's as easy as they come:
public class ObjectEquals : MarkupExtension, IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
return values.Length == 2 && values[0] == values[1];
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
}
It'd be so convenient if you could bind DataTrigger.Value to {Binding}, but it's not a dependency property.
You *could^ also do this purely in the viewmodel by temporarily removing SelectedAccount from TargetAccounts -- you'd have a private full _targetAccountsFull list, and a public filtered one. The setter for SelectedAccount would filter the list. Were you trying to do that already?
But that's not my idea of a good solution. Hiding combo box items is UI design stuff; the viewmodel shouldn't be involved in it, and in fact shouldn't even be aware that such things take place. One of the pleasures of WPF/MVVM is that you can separate that stuff out into pure UI code in the view. The viewmodel has its own complexities to worry about.
By the way, you bind SelectedItem to SelectedAccount, but SelectedAccount is an ObservableCollection. That makes no sense. There's one selected account. Make it a single Account, not a collection of them.
I am attempting to implement some simple validation on a textbox in MVVM
public string Property
{
get
{
if (App.PropertyStorageContainer != null)
{
return App.PropertyStorageContainer.Property;
}
else
{
return null;
}
}
set
{
App.PropertyStorageContainer.Property = value;
RaisePropertyChanged("Property");
}
}
Then in my PropertyStorageContainer class I have
private string _property;
public string Property
{
get
{
return App.PropertyStorageContainer.Property;
}
set
{
if(value meets some condition)
{
_property = value;
}
else
{
_property = someothervalue;
}
}
}
.
<TextBox Width="50" TextAlignment="Center" Text="{Binding Property, Mode=TwoWay, NotifyOnValidationError=True}" MaxLength="3"></TextBox>
The point of this is to validate what goes in the box. Now if I set this value directly from my code then everything works as I would expect. It attempts to SET the value, then calls RaiseProperyChanged, then GET the value (which because of the validation may not be the same value that was entered originally). The final value retrieved does show up on the view, so I know TwoWay binding is working.
The problem I have is when the input for SET comes from the bound XAML property / directy from user. In this case the SET method is called, the validation performed, but the GET never happens. This results in the unvalidated value remaining in the textbox on screen.
My first question would be is this a bug or expected behavior? I can see how maybe they tried to save performance by removing that last GET when the input came straight from the user since there should be nothing new to GET. But if not then maybe the way I have it all setup is interfering with the GET being called.
Second question is of course any suggestions for getting around this one. I've read a few suggestions for other methods of doing validation, but my program is already live on PROD and most of the changes being suggested involve a lot of rework for me so I am hoping to find a way to make it call GET any time the property is SET.
I have made a couple of assumptions since I am not sure I understand you code completely but I think you could consider possibly implementing a custom validation rule. First off, since your custom ValidationRule will take care of the validation you could get the logic out of your model class's property definition and "dumb down" your poco:
class PropertyStorageContainer
{
public string Property { get; set; }
}
It seems you desire your view model to act as a basic wrapper around your model class. Again, I will assume this is valid based on the description of your scenario:
class PropertyStorageContainerViewModel : INotifyPropertyChanged
{
private PropertyStorageContainer model;
public PropertyStorageContainerViewModel(PropertyStorageContainer model)
{
this.model = model;
}
public string Property
{
get
{
if (model != null)
{
return model.Property;
}
else
{
return null;
}
}
set
{
if (model.Property != value)
{
model.Property = value;
RaisePropertyChanged("Property");
}
}
}
// INotifyPropertyChanged implementation...
}
Now create a new class that extends System.Windows.Controls.ValidationRule and override the abstract Validate method in order implement your validation logic. For the example, I created a rule that just checks if the string is null or empty (assuming that would be an invalid scenario):
class IsNullOrEmptyValidationRule : ValidationRule
{
public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
{
string s = (value ?? string.Empty).ToString();
if (string.IsNullOrEmpty(s))
{
// Invalid...
return new ValidationResult(false, "Please enter a value.");
}
else
{
// Valid...
return new ValidationResult(true, null);
}
}
}
Now for the XAML... Here is an example of a TextBox that adds the validation rule to its binding validation rules (can be multiple rules).
<TextBox Name="textBox1" Width="50" FontSize="12"
Validation.ErrorTemplate="{StaticResource validationTemplate}"
Style="{StaticResource textBoxInError}">
<TextBox.Text>
<Binding Path="Property" UpdateSourceTrigger="PropertyChanged" >
<Binding.ValidationRules>
<local:IsNullOrEmptyValidationRule />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
Then define the following resources (referenced above) somewhere (e.g., Window.Resources). First a ControlTemplate to define how the TextBox should look when in invalid state:
<ControlTemplate x:Key="validationTemplate">
<DockPanel>
<TextBlock Foreground="Red" FontSize="15" Text="!!!" />
<AdornedElementPlaceholder/>
</DockPanel>
</ControlTemplate>
Additionally you could define a style trigger to display the error message. Here I just bind it to the ToolTip property of the TextBox:
<Style x:Key="textBoxInError" TargetType="{x:Type TextBox}">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={x:Static RelativeSource.Self},
Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
You're going into INPC hell right now. I've been there and it's not fun.
That's a big no-no, especially since if any mapping is done on such classes, those getters and setters will be called outside of their WPF binding context and hell breaks lose.
Keep it simple: bind straight to App.PropertyStorageContainer.Property
For the second case, either:
Use data validation
Let the property be set not by binding but through a Command, in which you can do such value swap.
Do yourself a favor and don't abuse properties' get/set
I want to conditionally load a UserControl according to a property. If the property is "true" then we load the UserControl in XAML. Let's say the property is named: IsCameraSupported. Set the Visibility to Collapsed should NOT be the right solution, since I totally do not want to include it in the XAML file.
Can someone give me a code example to do this, in XAML only?
Thank you very much.
EDIT
I misunderstood the question at first glance. This is the updated response.
You can use Frame control. In the Frame control, you can navigate to Page having camera control.
How to call view method from View model
Fire an event from IsCameraSupported setter in View Model.
Subscribe View to View Model event
Call Frame.Navigate in the event handler in view code behind.
ORIGINAL ANSWER
You can create BooleanToVisibilityConverter, and use DataBinding.
http://www.rhyous.com/2011/02/22/binding-visibility-to-a-bool-value-in-wpf/
http://msdn.microsoft.com/en-us/library/system.windows.controls.booleantovisibilityconverter.aspx
Converter code
public class BooleanVisibilityConverter : IValueConverter
{
#region Constructors
public BooleanVisibilityConverter()
: this(true)
{ }
public BooleanVisibilityConverter(bool collapseWhenInvisible)
: base()
{
CollapseWhenInvisible = collapseWhenInvisible;
}
#endregion
#region Properties
public bool CollapseWhenInvisible { get; set; }
public Visibility FalseVisibility
{
get
{
if (CollapseWhenInvisible)
return Visibility.Collapsed;
else
return Visibility.Hidden;
}
}
#endregion
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value == null)
return Visibility.Visible;
if ((bool)value)
return Visibility.Visible;
else
return FalseVisibility;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value == null)
return true;
return ((Visibility)value == Visibility.Visible);
}
#endregion
}
Use a Trigger or an Converter class that sets the visibility to collapsed when yr bool is true
during runtime yo can do this
if(IsCameraSupported)
{
var myControl = new MyControl();
MyCanvas.Children.Add(myControl);
Canvas.SetLeft(myControl, 20);
Canvas.SetTop(myControl, 20);
}
I would use a ContentControl and in a DataTrigger set the ContentTemplate to your UserControl if IsCameraSupported is True
<DataTemplate x:Key="MyUserControlTemplate">
<local:MyUserControl />
</DataTemplate>
<ContentControl>
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Style.Triggers>
<DataTrigger Property="{Binding IsCameraSupported}" Value="True">
<Setter Property="ContentTemplate" Value="{StaticResource MyUserControlTemplate}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>