binding Static Resource for ContentTemplate - xaml

How do I use a template dynamically based on a Bound string (This complains that the Static Resource must be a string, even thought the Binding will return a string):
<ContentControl ContentTemplate="{StaticResource {Binding Template}}">
<ContentPresenter />
</ContentControl>
So Template is actually just a string that will return which template to use?

Related

Xamarin Forms conditonal formatting based on data with DataTrigger

I am developing a chat application in Xamarin Forms and I am trying to add conditional formatting depending on whether it is an incoming or outgoing message.
This is my XAML:
<Frame
Margin="1"
Padding="0"
x:Name="FrameRef"
x:DataType="model:ChatMessage">
<Frame
CornerRadius="10"
Padding="7"
BackgroundColor="LightBlue"
HasShadow="false"
Margin="10,10,80,0">
<Frame.Triggers>
<DataTrigger
TargetType="Frame"
Binding="{Binding Source={x:Reference FrameRef}, Path=x:DataType.From}" Value="+1456456456">
<Setter Property="BackgroundColor" Value="Yellow"/>
</DataTrigger>
</Frame.Triggers>
When I use Path="Margin" and Value="1" it works.
I am now trying to make it work with the Path being x:DataType="model:ChatMessage" and checking the 'from'-field (indicating if the message was incoming or outgoing).
I'm not sure the Data Trigger is quite right for this application, since you're really depending on a data type and not really the content per se of another field. From the documentation:
The DataTrigger class is suitable for checking values on other controls, as well as any property on the control to which it has been added.
What you probably want instead is a Value Converter that handles locating a StaticResource and applying a style for you based on the message type. Full Microsoft Documentation here.
On your XAML element, you'd do something like this:
<Frame Style="{Binding foo, Converter={StaticResource FooToStyleConverter}}"/>
Your converter would work something like this:
public class FooToStyleConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var someValue = (DataTye)value; // Convert 'object' to whatever type you are expecting
// evaluate the converted value
if (someValue.From != null && someValue.From == Enum.SomeoneElse)
return (Style)App.Current.Resources["StyleReceived"]; // return the desired style indicating the message is from someone else
return (Style)App.Current.Resources["StyleSent"]; // return a style indicating the message is from the sender
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
// Usually unused, but inverse the above logic if needed
throw new NotImplementedException();
}
}
Lastly, set up your converter as a Static Resource in App.xaml (or as a local resource on the page) so your page can properly reference it
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:DataBindingDemos">
<ContentPage.Resources>
<ResourceDictionary>
<local:FooToStyleConverter x:Key="FooToStyleConverter" />
....

why does x:Bind expect a static method to bind to?

I have a UWP MVVM application where I bind, amongst others, the following property to a DataGridComboBoxColumn:
public List<ComboBoxValues> ListValues { get; set; } = new List<ComboBoxValues>();
XAML:
xmlns:local="using:MyProject.ViewModels"
<controls:DataGridComboBoxColumn Header="myHeader"
Binding="{Binding theSelectedValue, Mode=TwoWay}"
ItemsSource="{x:Bind local:PageVM.ListValues, Mode=OneWay}"
DisplayMemberPath="theValueOptions"/>
I use dependency injection, using Autofac to generate an instance of my viewModels when needed:
var containerBuilder = new ContainerBuilder();
containerBuilder.RegisterType<PageVM>().AsSelf();
I get the error: Static method 'ListValues' not found in type 'PageVM'. I have tried googling it, and the only results that I find is that it should not be that easy to bind to a static property etc.
Why is x:bind expecting a static method? I don't want to use static properties/methods.
EDIT:
The DataContext of pages are also set using a NavigationService and ViewModelBinder with code, though NOT the code-behind page. Autofac, the IoC manages the instances of those, the NavigationService and the ViewModelBinder. Thus, I do not know how to link the XAML to those instances to use x:Bind?
I do not want to use code behind, as I am trying to strictly stick to MVVM.
Yeah so the problem is the below line:
ItemsSource="{x:Bind local:PageVM.ListValues, Mode=OneWay}"
What you're doing with this code is, telling the xaml BindingEngine to look for a class called PageVM under the xmlns:local and then look for a field called ListValues. Now since we don't have an instance of the PageVM (as per the above line), it's considering ListValues as static and is trying to find it.
Generally to bind to a ViewModel, you set the DataContext of the Page or UserControl. You can do so like:
<Page.DataContext>
<local:PageVM x:Name="ViewModel"/>
</Page.DataContext>
if you're using a UserControl the above would look like:
<UserControl.DataContext>
<local:PageVM x:Name="ViewModel"/>
</UserControl.DataContext>
and now use it in xaml code like below:
<controls:DataGridComboBoxColumn Header="myHeader"
Binding="{Binding theSelectedValue, Mode=TwoWay}"
ItemsSource="{x:Bind ViewModel.ListValues, Mode=OneWay}"
DisplayMemberPath="theValueOptions"/>
Notice the x:Name="ViewModel", as part of the DataContext. This would allow you to reference your ViewModel via the codebehind too.
Edit After comments
Alternatively, if you cannot have a public parameterless constructor then I suggest you use the codebehind to create an instance of the ViewModel like below:
public PageVM ViewModel => this.DataContext as PageVM;
There is no clause that performs a null check on the above property since from the comments, the data context is being set via a dependency injection framework
and then use it the same way in your xaml code:
ItemsSource="{x:Bind ViewModel.ListValues, Mode=OneWay}"

Property can't be found on ViewModel in UWP app

An Order form in UWP using Template 10 adds products to an order. The error is
Invalid binding path 'OrderViewModel.FindProduct_TextChanged' : Property 'OrderViewModel' can't be found on type 'ProductViewModel'
The relevant xaml snippet is
<Page.DataContext>
<ViewModels:MainPageViewModel x:Name="OrderViewModel" />
</Page.DataContext>
<GridView ItemsSource="{x:Bind OrderViewModel.Products, Mode=TwoWay}">
<GridView.ItemTemplate>
<DataTemplate x:DataType="ViewModels:ProductViewModel" >
<AutoSuggestBox
Name="ProductAutoSuggestBox"
TextMemberPath="{x:Bind ItemCode, Mode=TwoWay}"
TextChanged="{x:Bind OrderViewModel.FindProduct_TextChanged}">
</AutoSuggestBox>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
The relevant snippet from the OrderViewModel and the ProductViewModel
namespace ViewModels
{
public class OrderViewModel : ViewModelBase
{
public ObservableCollection<Product> Products { get; set; } = new ObservableCollection<Product>();
public void FindProduct_TextChanged(AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args)
{ ... }
}
public class ProductViewModel : ViewModelBase
{
string _ItemCode = default(string);
public string ItemCode { get { return _ItemCode; } set { Set(ref _ItemCode, value); } }
public ProductViewModel()
{
}
}
}
How to I correctly reference FindProduct_TextChanged on the OrderViewModel from the DataTemplate for the GridView which references ProductViewModel?
Voted up to #tao's comment. #Vague, I think you may misunderstand what x:DataType is used for. You can refer to the "DataTemplate and x:DataType" part of Data binding in depth:
When using {x:Bind} in a data template, so that its bindings can be validated (and efficient code generated for them) at compile-time, the DataTemplate needs to declare the type of its data object using x:DataType.
For your scenario, from your code public ObservableCollection<Product> Products { get; set; } = new ObservableCollection<Product>();, the type of your DataTemplate's data object should be your Product class, not your ProductViewModel, and in the meanwhile, your FindProduct_TextChanged event must be find in this Product class, that means your code of FindProduct_TextChanged should be placed in your Product data model.
By the way, I think there is no need to use TwoWay binding for ItemsSource. For this scenario, the binding target is ItemsSource of GridView, the binding source is ObservableCollection<Product> Products, I understand you want to update GridView when your collection is updated, this is work is done with ObservableCollection. Besides, only the binding source here can be changed to notify the binding target, so OneWay binding is enough. But it's not a big problem with your code.
So for your GridView, it should be something like this:
<GridView ItemsSource="{x:Bind OrderViewModel.Products, Mode=OneWay}">
<GridView.ItemTemplate>
<DataTemplate x:DataType="Models:Product" >
<AutoSuggestBox
Name="ProductAutoSuggestBox"
TextMemberPath="{x:Bind ItemCode, Mode=TwoWay}"
TextChanged="{x:Bind FindProduct_TextChanged}">
</AutoSuggestBox>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
if error is kind of like this I approved its a charset support bug:
Error Invalid binding path 'XX.YY' : Property 'ZZ' can't be found on type 'CCC'
Either xaml and C# supports unicode;
its because you use a non-ascii character in class properties. this is a bug I found today. Just rename your class proprty characters to ascii standards. Hope it will be fixed.

How to execute command from GridViewItem Tap event (XAML)

I am trying to follow the MVVM pattern in my Windows 8.1 store app (XAML).
I want to navigate to a new view when a GridViewItem is clicked / tapped in the UI. I wanted to do this without code behind events to promote testability (using MVVM Light).
In order to allow my UI to bind to a view model command I have been looking at the Microsoft Behaviors SDK (XAML) added via Add References -> Windows -> Extensions.
The following code in my view compiles but blows up when I tap the grid view item. Unfortunately it offers little help & just throws an unhandled win32 exception [3476].
Can somebody please help shed some light on the problem?
Namespaces used are;
xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
xmlns:core="using:Microsoft.Xaml.Interactions.Core"
<GridView x:Name="itemGridView"
AutomationProperties.AutomationId="ItemGridView"
AutomationProperties.Name="Grouped Items"
ItemsSource="{Binding Source={StaticResource GroupedSource}}"
IsSwipeEnabled="True"
IsTapEnabled="True">
<GridView.ItemTemplate>
<DataTemplate>
<Grid Margin="0"
Height="230">
<StackPanel Orientation="Vertical"
HorizontalAlignment="Stretch">
<Image Source="{Binding Image}"
Stretch="UniformToFill"
HorizontalAlignment="Center"
VerticalAlignment="Center"
/>
<StackPanel VerticalAlignment="Bottom"
Height="45"
Margin="0,-45,0,0">
<StackPanel.Background>
<SolidColorBrush Color="Black"
Opacity="0.75"
/>
</StackPanel.Background>
<TextBlock FontSize="16"
Margin="2"
Text="{Binding Name}"
TextWrapping="Wrap"
VerticalAlignment="Bottom"
/>
</StackPanel>
</StackPanel>
<interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="Tapped">
<core:InvokeCommandAction Command="{Binding DataContext.SummaryCatagorySelectedCommand, ElementName=LayoutRoot}" />
</core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
</Grid>
</DataTemplate>
</GridView.ItemTemplate>
Edit. As requested, I've added the view model, containing specifically the command I want to fire from my behavior.
public class ViewModel : ViewModelBase
{
public RelayCommand<string> SummaryCatagorySelectedCommand { get; set; }
public ViewModel()
{
//
}
}
The simplest answer is to tell you that you should not use a command in this situation. First, the value of a command is that it both executes and communicates back the inability to execute to the interactive XAML control. For example, the button is disabled when the command is not available.
But since you are using the tapped event of a framework element, you are basically just using the control as if it is a simple method, and not a command at all. Your view model can have both commands and methods, of course. And behaviors can call both commands and methods.
To that end, the best scenario here for your solution is to change your approach from calling a command in your view model. Your difficulties are 1. the command is out of scope of the data template and 2. the command parameter is passed inside an out of scope threading context.
Here's what I would suggest to make your life easier and your app simpler.
Do not attach to the tapped event of the item. But instead attach to the itemclicked event of the gridview. This, of course, means you need to set IsItemClickEnabled to true on your gridview. Then don't call to a command, which is overhead you are not using, but instead call to a method.
This is what the method would look like in your viewmodel:
public async void ClickCommand(object sender, object parameter)
{
var arg = parameter as Windows.UI.Xaml.Controls.ItemClickEventArgs;
var item = arg.ClickedItem as Models.Item;
await new MessageDialog(item.Text).ShowAsync()
}
The name of the method does not matter (I even called it a Command to make the point), but the signature does. The behavior framework is looking for a method with zero parameters or with two object-type parameters. Conveniently, the two parameter version gets the event signature forwarded to it. In this case, that means you can use the ItemClickEventArgs which contains the clicked item. So simple.
Your gridview is simplified, too. Instead of trying to force the scope inside your data context you can simply reference the natural scope of the gridview to the outer viewmodel. It would look something like this:
<GridView Margin="0,140,0,0" Padding="120,0,0,0"
SelectionMode="None" ItemsSource="{Binding Items}"
IsItemClickEnabled="True">
<Interactivity:Interaction.Behaviors>
<Core:EventTriggerBehavior EventName="ItemClick">
<Core:CallMethodAction MethodName="ClickCommand"
TargetObject="{Binding Mode=OneWay}" />
</Core:EventTriggerBehavior>
</Interactivity:Interaction.Behaviors>
It's such a simpler solution, and doesn't violate anything in the MVVM pattern because it still pushes the logic out to your separated and testable viewmodel. It lets you effectively use behaviors as an event-to-command but actually using a simpler event-to-method pattern. Since the behavior doesn't pass the CanExecute value back to the control in the first place, this actually simplifies your viewmodel, too.
If, it turns out what you are wanting to do is reuse an existing command that is already leveraged elsewhere (which sounds like the 1% edge case) you can always create a shell method for this purpose that internally leverages the command for you.
As a warning, the RelayCommand that ships with Windows 8.1 does not properly implement ICommand as it does not first test CanExecute before Execute is invoked. In addition, the CanExecute logic in the typed RelayCommand does not pass the CommandParameter to the handler. None of this really matters, depending on who you are using commands in the first place. It matters to me though.
So, that's your answer. Change to GridView.ItemClicked and change from ViewModel.Command to ViewModel.Method. That makes your life easier, far easier, and makes your XAML more portable should you ever want to reuse your data template.
Best of luck!
When you tap on any item you change SelectedItem, I guess. You can Binding (Mode=TwoWay) SelectedItem and in Set() of property raise needed action.
Or you can use something like this and use as dependency property of your GridView.
public class GridViewItemClickCommand
{
public static readonly DependencyProperty CommandProperty =
DependencyProperty.RegisterAttached("Command", typeof(ICommand),
typeof(GridViewItemClickCommand), new PropertyMetadata
(null, CommandPropertyChanged));
public static void SetCommand(DependencyObject attached, ICommand value)
{
attached.SetValue(CommandProperty, value);
}
public static ICommand GetCommand(DependencyObject attached)
{
return (ICommand)attached.GetValue(CommandProperty);
}
private static void CommandPropertyChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
// Attach click handler
(d as GridView).ItemClick += gridView_ItemClick;
}
private static void gridView_ItemClick(object sender,
ItemClickEventArgs e)
{
// Get GridView
var gridView = (sender as GridView);
// Get command
ICommand command = GetCommand(gridView);
// Execute command
command.Execute(e.ClickedItem);
}
}
If you have any problems, please ask :)
I had the same issued but I solved it in another way. I don't put the behavior insie the datatemplate, I do it in the GridView :
<GridView x:Uid="Flow"
x:Name="PlacesGridView"
Grid.Column="1" Grid.Row="2"
ItemTemplate="{StaticResource YourDataTemplate}"
ItemsSource="{Binding YourSource}" >
<Interactivity:Interaction.Behaviors>
<Behaviors:GoToDestinationOnSelected/>
</Interactivity:Interaction.Behaviors>
</GridView>
This is how GoToDestinationOnSelected looks like:
public class GoToDestinationOnSelected : DependencyObject, IBehavior
{
.....
void GoToDestinationOnGridViewItemSelected_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
object obj = (sender as ListViewBase).SelectedItem;
if (obj == null)
return;
if (obj is YourClass)
{
App.RootFrame.Navigate(typeof(CountryPlacesPage));
return;
((AssociatedObject) as ListViewBase).SelectedIndex = -1;
}
public DependencyObject AssociatedObject
{
get;
private set;
}
public void Attach(DependencyObject associatedObject)
{
AssociatedObject = associatedObject;
(associatedObject as ListViewBase).SelectionChanged += GoToDestinationOnGridViewItemSelected_SelectionChanged;
}
public void Detach()
{
(AssociatedObject as ListViewBase).SelectionChanged -= GoToDestinationOnGridViewItemSelected_SelectionChanged;
}
~GoToDestinationOnSelected()
{
}
In UWP(Window 10 and newer) Mobile App, apply the below code snippet
<Interactivity:Interaction.Behaviors>
<Core:EventTriggerBehavior EventName="ItemClick">
<Core:EventTriggerBehavior.Actions>
<Core:`enter code here`InvokeCommandAction Command="{Binding itemclick}"/>
</Core:EventTriggerBehavior.Actions>
</Core:EventTriggerBehavior>
</Interactivity:Interaction.Behaviors>

Is there something similar to TabControlRegionAdapter.ItemContainerStyle Attached Property for ItemsControl?

I'm using Prism 4 with Silverlight and I want to use ItemsControl to host multiple views. I really want all the views to be wrapped inside a specified ItemTemplate or be able to specify an ItemStyle so that I can use something like the Expander control in the Silverlight Toolkit . When I try to specify an ItemTemplate an unhandled System.NotSupportedException is thrown at runtime.
ItemsControl.Items must not be a UIElement type when an ItemTemplate is set.
at System.Windows.Controls.ItemsControl.MS.Internal.Controls.IGeneratorHost.GetContainerForItem(Object item, DependencyObject recycledContainer)
at System.Windows.Controls.ItemContainerGenerator.Generator.GenerateNext(Boolean stopAtRealized, Boolean& isNewlyRealized)
at System.Windows.Controls.ItemContainerGenerator.System.Windows.Controls.Primitives.IItemContainerGenerator.GenerateNext(Boolean& isNewlyRealized)
at System.Windows.Controls.ItemsControl.AddContainers()
at System.Windows.Controls.ItemsControl.RecreateVisualChildren(IntPtr unmanagedObj)
Code
<ItemsControl Regions:RegionManager.RegionName="DetailsRegion">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border BorderBrush="Red" BorderThickness="1">
<ContentPresenter Content="{Binding}"/>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
It has been a while since I used PRISM, but the following is an example that you can use to implement a custom IRegion that wraps the element before adding it to the collection.
public class RegionWrapper : Region
{
public override Microsoft.Practices.Composite.Regions.IRegionManager Add(object view, string viewName, bool createRegionManagerScope)
{
var myWrapper = new Wrapper();
myWrapper.Content = view;
return base.Add(myWrapper, viewName, createRegionManagerScope);
}
}
To register this item you need to create a Region factory, which in PRISM they call an adapter
public class RegionWrapperAdapter : RegionAdapterBase<IRegionAdapter>
{
protected override Microsoft.Practices.Composite.Regions.IRegion CreateRegion()
{
return new RegionWrapper();
}
}
Then on your Bootstrap just register your adapter
protected override RegionAdapterMappings ConfigureRegionAdapterMappings()
{
var regionAdapterMappings = base.ConfigureRegionAdapterMappings();
regionAdapterMappings.RegisterMapping(typeof(ItemsControl), Container.Resolve<RegionWrapperAdapter>());
return regionAdapterMappings;
}
Of course the other part left is to implement the control 'Wrapper' so you can create that class and add the content. It could simply be a ContentControl with a particular style similar to what you have in this example or add anything fancier.
This code is based on an old version of PRISM, so things might have changed recently.
Hope this helps
Miguel