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>
Related
I am having trouble with what seems like a fairly straightforward task. I have a treeview with nodes, and I would like to set the TreeViewItem 'IsExpanded' property to True for the whole treeview if a specific string property 'SearchTerm' of my View Model is not empty. In other words, if string property is not null, IsExpanded value should be True. I have already done this in codebehind but I prefer to do this in XAML for cleanness.
To describe the code below, I created a converter which will convert a null string to 'False' and non-null to 'True'. In my XAML I call this converter when I attempt to bind the string value from the viewmodel in the TreeView ItemContainerStyle. It appears that the converter is never even fired.
My XAML (simplified):
<UserControl.Resources>
<cv:ExpandNodesIfSearchConverter x:Key="ExpandAll">
</cv:ExpandNodesIfSearchConverter>
</UserControl.Resources>
<TreeView Grid.Row="2" x:Name="myTreeView"
ItemsSource="{Binding Sponsors}"
SelectedItemChanged="TreeView_SelectedItemChanged" >
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<!-- if SearchTerm is not null, use converter to set value to true and expand all nodes -->
<Setter Property="IsExpanded" Value="{Binding Path=SearchTerm, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource ExpandAll}}" />
</Style>
</TreeView.ItemContainerStyle>
<!-- TreeView data -->
</TreeView>
My View Model:
public class TreeViewVM : INotifyPropertyChanged
{
private string _searchterm;
public string SearchTerm
{
get
{
return _searchterm;
}
set
{
_searchterm = value;
OnPropertyChanged("SearchTerm");
}
}
}
My Converter:
class ExpandNodesIfSearchConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
//if searchterm is not null, return true to expand all items, otherwise return false
string searchterm = value.ToString();
if (string.IsNullOrEmpty(searchterm))
return false;
else
return true;
}
public object ConvertBack(object value, Type targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
I solved my issue by using the ElementName in the Setter tag rather than the viewmodel property. searchTxt being the name of the TextBox which SeachTerm was bound to.
<Setter Property="IsExpanded" Value="{Binding ElementName=searchTxt, Path=Text, Mode=OneWay, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource ExpandAll}}" />
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.
I don't understand something about how to set object attribute for xaml about boolean..
I have a MainPage.xaml like this where I set ProportionalSize to true:
<ContentPage.Resources>
<ResourceDictionary>
<converter:BooleanConverter x:Key="Boolean"/>
</ResourceDictionary>
</ContentPage.Resources>
<ContentPage.Content>
<!-- Background during loading of start -->
<AbsoluteLayout>
<local:CustomImage Source="{extension:ImageResource HomeBG.png}"
ProportionalWidth="100" ProportionalHeight="100" ProportionalSize="{True, Converter={StaticResource Boolean}}"
AbsoluteLayout.LayoutBounds="0.5, 0.5, 1, 1"
AbsoluteLayout.LayoutFlags="All"/>
</AbsoluteLayout>
</ContentPage.Content>
I use a customImage for some reason, this is the class
public class CustomImage : Image
{
private bool _ProportionalSize;
public bool ProportionalSize
{
get { return this._ProportionalSize; }
set
{
this._ProportionalSize = value;
TrySize();
}
}
}
Because neither true nor True works, I made a BooleanConverter
public class BooleanConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return (bool)value;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return (bool)value;
}
}
However, it still doesn't work...
Additional information: Position 19:75. MarkupExtension not found for true
ProportionalSize="{True, Converter={StaticResource Boolean}}"
Does I'm doing something wrong?
Just set the value, you don't need to use markup extension syntax (those "{}" brackets) or a converter:
ProportionalSize="True"
If you're not actually binding to a value that will change... Don't use a converter or property. It looks like you just want to set true one time in XAML. You can use the x:Arguments attribute for that.
<x:Arguments>
<x:Boolean>True</x:Boolean>
</x:Arguments>
Bigger example from a DataTrigger.
Usecase - The grid has a different value binded to IsVisible, but we want to override that if the administrator is logged in. So in addition to the regular binding, we can put in a datatrigger that uses the x:Arguments of x:Boolean to set it to true. - Without a converter... without a property.
<Grid.Triggers>
<DataTrigger Binding="{Binding IsAdmin}"
TargetType="{x:Type Grid}"
Value="True">
<Setter Property="IsVisible">
<Setter.Value>
<x:Arguments>
<x:Boolean>True</x:Boolean>
</x:Arguments>
</Setter.Value>
</Setter>
</DataTrigger>
</Grid.Triggers>
Try to use BindableProperty:
public static readonly BindableProperty ProportionalSizeProperty =
BindableProperty.Create(nameof(ProportionalSize),
typeof(bool),
typeof(CustomImage),
default(bool),
propertyChanged: OnProportionalSizeChanged);
public bool ProportionalSize
{
get { return (bool)GetValue(ProportionalSizeProperty); }
set { SetValue(ProportionalSizeProperty, value); }
}
private static void OnProportionalSizeChanged(BindableObject bindable, object oldValue, object newValue)
{
var customImage = bindable as CustomImage;
if (customImage != null)
{
customImage.TrySize();
}
}
If IsEnabled property is true, I need to set the Style attribute otherwise it shouldn't be set. In the examples I've seen so far, style properties are set but not the style attribute itself. Below code is not working using Triggers.
<TabItem.Style>
<Style TargetType="TabItem">
<Style.Triggers>
<DataTrigger Binding="{Binding IsEnabled}" Value="True">
<Setter Property="Style" Value="DotcomTabItemStyle" />
</DataTrigger>
</Style.Triggers>
</Style>
</TabItem.Style>
Since you're setting the Trigger through Style, changing the Style would also remove the Trigger... Not really sure if that'll work out :P
Anyway, you're making a mistake on your Setter (setting the resource name directly, not through a static or dynamic resource reference). And you don't need a DataTrigger. It should be:
<Trigger Property="IsEnabled" Value="True">
<Setter Property="Style" Value="{StaticResource DotcomTabItemStyle}" />
</Trigger>
But as I said, this won't probably work as intended, since you're trying to modify the Style property from within the current Style...
One way or another, you'll end up adding different Setters for each property, probably either modifying the DotcomTabItemStyle Style you already have, or creating a new one (based on that one, maybe).
EDIT - Or you could use a Converter and bind the Style property to the IsEnabled property.
I've created a reusable Converter for all this kind of situations:
public class ConditionalSetterConverter : IValueConverter
{
public bool Inverse { get; set; }
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
bool flag = (bool)value;
if (flag ^ Inverse)
return parameter;
else
return Binding.DoNothing;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
You use it like this:
<Window>
<Window.Resources>
<converters:ConditionalSetterConverter x:Key="InverseConditionalSetterConverter"
Inverse="True" />
<Style x:Key="DotcomTabItemStyle" TargetType="TabItem">...</Style>
</Window.Resources>
<TabControl>
<TabItem Style="{Binding IsEnabled,
RelativeSource={RelativeSource Mode=Self},
Converter={StaticResource InverseConditionalSetterConverter},
ConverterParameter={StaticResource DotcomTabItemStyle}}" />
</TabControl>
</Window>
EDIT 2 - OR... You could use a Style selector. ItemsControls like TabControl have a property called ItemContainerStyleSelector, of type StyleSelector.
You'd have to create your own class, inheriting StyleSelector, and override the SelectStyle function to include your custom logic there.
Something like this:
public class DotcomTabItemStyleEnabledSelector : StyleSelector
{
private Style style = null;
public override System.Windows.Style SelectStyle(object item, System.Windows.DependencyObject container)
{
var tabItem = container as TabItem;
if (tabItem != null && tabItem.IsEnabled)
{
if (style == null)
style = textBox.TryFindResource("DotcomTabItemStyle") as Style;
return style;
}
return null;
}
}
I've never used Style selectors, so I'm not really sure if this would work out of the box, but at least you get the idea.
I have a requirement to change a button's style based on a value in the data. It looks like a StyleSelector would work perfectly but there doesn't seem to be a way to set one for a button.
Is there a way to set a button style dynamically from data? Maybe even a pure XAML approach?
A more general way to accomplish the same thing:
SomeView.xaml
<UserControl>
<UserControl.Resources>
<converters:BooleanToStyleConverter x:Key="MyButtonStyleConverter"
TrueStyle="{StaticResource AmazingButtonStyle}"
FalseStyle="{StaticResource BoringButtonStyle}"/>
</UserControl.Resources>
<Grid>
<Button Style={Binding IsAmazingButton, Converter={StaticResource MyButtonStyleConverter}}/>
</Grid>
</UserControl>
BooleanToStyleConverter.cs
public class BooleanToStyleConverter : IValueConverter
{
public Style TrueStyle { get; set; }
public Style FalseStyle { get; set; }
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value is bool && (bool) value)
{
return TrueStyle;
}
return FalseStyle;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
This converter works in any view with any kind of control using whatever style you choose as long as you are binding to a Boolean property in your ViewModel to control the style switching. Easy to adapt it to other binding requirements though. This works in a DataTemplate as well.
You could place your Button Styles in a Resource Dictionary and bind the Style for the Button and use a Converter
ButtonStyles.xaml
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style x:Key="ButtonStyle1" TargetType="Button">
<Setter Property="Background" Value="Green"/>
<Setter Property="FontSize" Value="12"/>
</Style>
<Style x:Key="ButtonStyle2" TargetType="Button">
<Setter Property="Background" Value="Red"/>
<Setter Property="FontSize" Value="14"/>
</Style>
</ResourceDictionary>
Then for the Button that has this requirement you bind Style to the property of interest
<Button ...
Style="{Binding Path=MyDataProperty,
Converter={StaticResource ButtonStyleConverter}}"/>
And in the Converter you load the ButtonStyles Resource Dictionary and return the desired Style based on the value
public class ButtonStyleConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
Uri resourceLocater = new Uri("/YourNameSpace;component/ButtonStyles.xaml", System.UriKind.Relative);
ResourceDictionary resourceDictionary = (ResourceDictionary)Application.LoadComponent(resourceLocater);
if (value.ToString() == "Some Value")
{
return resourceDictionary["ButtonStyle1"] as Style;
}
return resourceDictionary["ButtonStyle2"] as Style;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}