I have a ListView that's on a ContentPage, and I want the ItemTemplate of the ListView to be user-definable - i.e. parsing the XAML for the template at runtime. However, I need to give the user the option of having a button that invokes a command on the page. See below XAML;
<SwipeView>
<SwipeView.RightItems>
<SwipeItems>
<SwipeItem
Text = ""Delete""
BackgroundColor=""Red""
Command=""{Binding Path=ItemDeletedCommand, Source={x:Reference multiEntryPage}}""
CommandParameter=""{Binding .}"" />
</SwipeItems>
</SwipeView.RightItems>
<SwipeView.Content>
...
</SwipeView.Content>
And my content page has the x:Name="multiEntryPage" attribute in its ContentPage element. The code I use to parse the XAML is shown here:
listView.ItemTemplate = new DataTemplate(() => new ViewCell().LoadFromXaml(xaml));
However, this gives the below error:
{Xamarin.Forms.Xaml.XamlParseException: Position 11:33. Can not find
the object referenced by multiEntryPage at
Xamarin.Forms.Xaml.ApplyPropertiesVisitor.ProvideValue (System.Object&
value, Xamarin.Forms.Xaml.ElementNode node, System.Object source,
Xamarin.Forms.Xaml.XmlName propertyName)...
So I think it's fairly clear that the error is happening because the SwipeView XAML doesn't know about its parent at this stage, but I'm at a loss of how to resolve this. Any ideas?
Thanks!
You can create DataTemplate in following ways
Creating an Inline DataTemplate
Creating a DataTemplate with a Type
Creating a DataTemplate as a Resource
Refer https://learn.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/templates/data-templates/creating.
Related
I have historically been using the syncfusion tab control as part of my layout on my apps, however, with the recent introduction of the Xamarin Community Toolkit (xct) and with the LazyView being in the latest pre-release nuget I want to use this and see what the experience is like.
I perform the registration as normal in the app.xaml.cs file as I always do:
ViewModelLocationProvider.Register<HomeView, HomeViewViewModel>();
When I am on my content page I add the view to a tab item:
<xct:TabViewItem Icon="{AppThemeBinding Light={local:ImageResource App.Assets.Icons.Black.home_100px.png}, Dark={local:ImageResource App.Assets.Icons.White.home_100px.png}}" Text="Home">
<xct:TabViewItem.Content>
<xct:LazyView x:TypeArguments="dressmakerViews:HomeView" />
</xct:TabViewItem.Content>
</xct:TabViewItem>
At this point the view is added as expected but is assigned the viewmodel of the contentpage instead of the viewmodel I registered for the view.
Obvious troubleshooting here was to use LazyView outside of the tabview and that assigned the expected viewmodel.
My question here is to help me understand why it does this and how to resolve the problem.
As a workaround to get the app working I've had to move out the code into the view model for the content page but everything here feels wrong and I want to be in a position to resolve.
For some reason, TabView replaces any previous BindingContext values assigned to TabItems with its own BindingContext when it changes..
As a work-around you can add a listener to BindingContextChanged event for each TabViewItem.Content and reassign to its expected ViewModel.
UPDATE:
You can also wrap TabView's Content with a ContentView
<xct:TabViewItem Icon="{AppThemeBinding Light={local:ImageResource App.Assets.Icons.Black.home_100px.png}, Dark={local:ImageResource App.Assets.Icons.White.home_100px.png}}" Text="Home">
<xct:TabViewItem.Content>
<ContentView>
<xct:LazyView x:TypeArguments="dressmakerViews:HomeView" />
</ContentView>
</xct:TabViewItem.Content>
</xct:TabViewItem>
That way, LazyView's BindingContext won't be overwritten.
i am just exploring windows phone runtime apps template. But i am seeing a weird thing.
I have Button defined in Xaml with ContentTemplate set in it. I wanted extract the Image control defined in the ContentTemplate of this button. But it is coming null.
Xaml code :-
<Button x:Name="PlayButton" Click="PlayButton_OnClick">
<Button.ContentTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Name="Panel">
<Image x:Name="ControlImg"
Width="100"
/>
<TextBlock Text="text block" />
</StackPanel>
</DataTemplate>
</Button.ContentTemplate>
Here is button Click event :-
private async void PlayButton_OnClick(object sender, RoutedEventArgs e)
{
var btn = sender as Button;
var ct = btn.ContentTemplate; // this part is also not showing controls in it when expending ct at runtime.
var img = btn.FindName("ControlImg") as Image; // coming null
var stckpnl = btn.FindName("Panel") as StackPanel;// coming null
}
Can anybody check this out why is this happening ?
Edit :- I have broken my problem and reach this very bottom simple level and after seeing this i am just not getting why is this happening ?
That is strange behavior. It should have stack Panel and image in control template. As a work around you can use ContentTemplateRoot to get the image and stackpanel. I have test this, it is working.
((StackPanel)btn.ContentTemplateRoot).Children[0] // image
Hope this helps
Edit:
For Details about why FindName is not working see the the Remarks section on on MSDN
. Here is some relevant quotes
Important In order to use the FindName method effectively, you should understand the concept of a XAML namescope, and how a XAML namescope is created at XAML load time and then referenced and possibly modified at run time. For more info see XAML namescopes.
The most common usage of FindName in your Windows Runtime code will be from within the generated InitializeComponent call for a XAML page. In this situation, FindName is invoked only after the XAML page is loaded. InitializeComponent provides the infrastructure such that any object that was instantiated by XAML loading can conveniently be accessed by your code-behind code. You can then reference the objects as a variable that shares the same name as the markup-declared x:Name.
A run-time API such as FindName is working against a run-time object tree of the app as it exists in memory. When part of this object tree is created from templates or run-time loaded XAML, a XAML namescope is typically not contiguous within that object tree. The result is that there might be a named object in the object tree that a given FindName scope cannot find. The discontinuities between XAML namescopes that you might encounter in typical application scenarios are when objects are created by applying a template, or when objects are created by a call to XamlReader.Load and subsequently added to the object tree.
As you are using DataTemplate so the xaml object tree is not contiguous so that is why FindName is failed to find the control from the xaml tree.
hope this explains...
seems like a trivial task: i am building a wpf application, using MVVM pattern. what i want is dynamically change part of a view, using different UserControls, dependent on user input.
let's say, i have got 2 UserControls, one with a button, and another with a label.
in main view i have a container for that. following XAML "works":
<GroupBox Header="container" >
<local:UserControlButton />
</GroupBox>
and a UserControl element with buttons pops up. if i change it to another one, it works too.
question is how to feed that groupbox dynamically. if i put something like that in my model view:
private UserControl _myControl;
public UserControl MyControl
{
get
{
return _myControl;
}
set
{
_myControl= value;
InvokePropertyChanged("MyControl");
}
}
and change my view XAML to something like:
<GroupBox Header="container" >
<ItemsControl ItemsSource="{Binding MyControl}" />
</GroupBox>
and feed it from command with usercontrol for button or for label: nothing happens, although "MyControl" variable is set and is "invoke property changed"..
Obviously there are many ways to skin this particular cat - but to answer the question of why it doesn't work you need to look into the ItemsSource property of ItemsControl on MSDN.
The items control is designed to show multiple items, provided through an IEnumerable passed to the ItemsSource property. You are passing a UserControl, so the binding will fail.
For your example, I would change the ItemsControl to a ContentControl and bind the content to your MyControl property. This should then work.
<GroupBox Header="container" >
<ContentControl Content="{Binding MyControl}" />
</GroupBox>
However, I would strongly recommend looking into other ways of doing this - having a control in your VM breaks MVVM to my mind. Depending on what you are doing look at data templates - #Sheridan's link in the comments provides an great description of a way to do it.
Couldn't post this as a comment so adding as answer..
Have a look at this:
Implementing an own "Factory" for reusing Views in WPF
It uses DataTemplates but doesn't require the DataTemplate section for each view. If you potentially have a lot of user controls/views you wish to display or you are reusing through multiple views or you are intending to actually dynamically generate a view (versus just loading an existing user control) then this might suite your needs.
I have a wizard created in WPF consisting of pages as UserControl objects. What I'm trying to do is to load plugins from .DLL files which contain the following:
A code file for the plugin logic.
A XAML user control which will present configuration options for the plugin, displayed in the main wizard.
A view model for the user control.
I've been able to successfully load in and instantiate the UserControl object as well as the View Model, and I have gotten to the stage where the Control appears in it's own wizard page as intended.
(This is probably the view model that's instantiated correctly, since I set the title for the wizard page in the view model and that all works fine)
The problem I'm getting is that the UserControl I've loaded from the DLL isn't displaying correctly. Instead of displaying the UserControl contents, it just shows the plaintext MyDLL.MyCustomUserControl from the line x:Class="MyDLL.MyCustomUserControl" where the actual user control should be.
The contents of my user control that I'm loading from the DLL is:
<UserControl x:Class="MyDLL.MyCustomUserControl"
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"
d:DesignHeight="300" d:DesignWidth="300">
<StackPanel>
<Label Content="This is the Plugin Options Page" />
<TextBox Text="{Binding Path=PluginStringText}" />
</StackPanel>
</UserControl>
The property PluginStringText exists in the View Model for this User Control.
I have a feeling I need to somehow assign or bind the View Model to the User Control after loading it in from the DLL.
As part of my wizard, there is a section where I define Data Templates (this is another UserControl which contains the Wizard's pages), but I don't know how to actually add extra templates during runtime. I have a feeling this is causing the issue.
Searching through many topics reveals I could probably do this through the code behind file for the view, but then I have no way of obtaining a reference to the view's code behind in the first place to call a method. Is there a way of adding a new DataTemplate entry from the View's View Model class?
<UserControl.Resources>
<DataTemplate DataType="{x:Type viewModel:ExistingPage1ViewModel}">
<view:ExistingPage1View />
</DataTemplate>
<DataTemplate DataType="{x:Type viewModel:ExistingPage2ViewModel}">
<view:ExistingPage2View />
</DataTemplate>
<...>
Thanks in advance if someone could point me in the right direction
I found a way to resolve this.
The problem was as I had expected, there was no mapping between the ViewModel data type and the View in which to render the ViewModel with.
I discovered a useful guide here http://www.ikriv.com/dev/wpf/DataTemplateCreation/ which explains how to create a new data template within code, very similar to if you'd hard coded the template in the XAML.
This was all well and good, but I still had to find a way to call the method to create the data template.
Well - It turned out I didn't need to call the method at all in the code behind! I just put the method to create the data template directly in my view model which is responsible for populating the wizard's pages.
This also has the added benefit that I do not have to hard code my existing pages datatemplate's in the xaml.
I will add the code referenced at http://www.ikriv.com/dev/wpf/DataTemplateCreation/ for future reference:
The method to create the DataTemplate object:
DataTemplate CreateTemplate(Type viewModelType, Type viewType)
{
const string xamlTemplate = "<DataTemplate DataType=\"{{x:Type vm:{0}}}\"><v:{1} /></DataTemplate>";
var xaml = String.Format(xamlTemplate, viewModelType.Name, viewType.Name, viewModelType.Namespace, viewType.Namespace);
var context = new ParserContext();
context.XamlTypeMapper = new XamlTypeMapper(new string[0]);
context.XamlTypeMapper.AddMappingProcessingInstruction("vm", viewModelType.Namespace, viewModelType.Assembly.FullName);
context.XamlTypeMapper.AddMappingProcessingInstruction("v", viewType.Namespace, viewType.Assembly.FullName);
context.XmlnsDictionary.Add("", "http://schemas.microsoft.com/winfx/2006/xaml/presentation");
context.XmlnsDictionary.Add("x", "http://schemas.microsoft.com/winfx/2006/xaml");
context.XmlnsDictionary.Add("vm", "vm");
context.XmlnsDictionary.Add("v", "v");
var template = (DataTemplate)XamlReader.Parse(xaml, context);
return template;
}
This DataTemplate then needs to be registered as an application resource:
Registering the DataTemplate:
Application.Current.Resources.Add(template.DataTemplateKey;, template);
Once again, this code was provided thanks to Ivan Krivyakov at http://www.ikriv.com/dev/wpf/DataTemplateCreation/
Question
Basically I would like to do the following but it seems that I cannot:
UserControl myControl = new UserControl();
DataTemplate template = new DataTemplate(myControl);
The question: Is it possible to construct a DataTemplate from UserControl instance? If not, are there any other possible solutions?
Real problem
I'm working on a project where majority of UI views are simple static Word-like documents (e.g some text fields and maybe some images, nothing too fancy). Because most of persons working on this project are not coders we have designed very simple in-house markup language for UI generation. An example of markup of simple view is following:
First name: [Person.FirstName]
Last name: [Person.LastName]
Address: [Person.Address.Street], [Person.Address.City]
Now these templates are loaded at runtime and usercontrols are created based on them. In this case one usercontrol would be created and it would contain simply couple of stack panels and text blocks so that resulting control would look a bit like text document. XAML equivalent would be something like:
<StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="First name: "/>
<TextBlock Text={Binding Person.FirstName}
</StackPanel>
<StackPanel>
...
</StackPanel>
...
</StackPanel>
Then, I started to implement support for lists but couldn't think of a way how to do that. In theory it is simple and I came up with following syntax (+ XAML equivalent):
[List Customers]
First name: [Person.FirstName]
Last name: [Person.LastName]
Address: [Person.Address.Street], [Person.Address.City]
[EndList]
->
<StackPanel>
<ItemsControl ItemsSource="{Binding Customers}">
<ItemsControl.ItemTemplate>
<DataTemplate>
[Insert code from previous XAML example here]
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
But I can't do that because it seems that I cannot construct DataTemplate directly from UserControl instance. There would be no problems if my UserControls were types, but they are not.
One possible solution is that I could bind ItemsControl.Items directly to list of UserControls (instead of binding to ItemsSource and ItemTemplate) but this is sub-optimal in our case for couple of reasons. I would be willing to try some other solutions first!
Update
For clarification: all I have is plain instance of UserControl class which contains the content I need. E.g.
UserControl control = new UserControl();
var panel = new StackPanel();
panel.Children.Add(...);
panel.Children.Add(...);
control.Content = panel;
// How to use that control as ItemTemplate for ItemsControl?
// It seems that it is not possible directly but I want to
// know what my options are.
I don't have class for it because I'm constructing it at run-time and I don't want to create new type dynamically by emiting IL code because it is way too painful.
Creating a datatemplate from Code behind goes like this:
FrameworkElementFactory factory = new FrameworkElementFactory(MyUserControl.GetType());
DataTemplate dt = new DataTemplate();
dt.VisualTree = factory;
yourItemsControlInstance.ItemTemplate = dt;
A datatemplate is a definition of controls to be built at runtime, that is way this construction with a ElementFactory. You do not want the same instance of the UserControl for every item in your ItemsControl.
Ah I understand your problem now. I don't think there is an easy way (one or two lines of code) to create a datatemplate from a UserControl instance.
But to solve your problem I see two directions:
At the point where an usercontrol is created, create a datatemplate instead and use that. It will be cumbersome, with nested FrameworkElementFactories. I have never done that, and the MSDN documentation says that you may encounter some limitations you cannnot do compared to datatemplates in Xaml. But if it is simple it must be doable. There used to be a codeproject article by Sacha Barber you could use as a guidance (if needed).
You pack the creation of the UserControl in a method called private UserControl createMyUserControl(){}
And do something like this:
ItemsControl itemsControl = new ItemsControl();
foreach (var customer in Customers)
{
var usercontrol = createMyUserControl(...);
usercontrol.DataContext = customer;
itemsControl.Items.Add(usercontrol);
}
Second option is less elegant in my opinion, so I would check out the option 1 first.
WPF: How to create Styles in code/and magical Content (see section at the end for extensive sample of a DataTemplate in Code behind)
I think you can replace UserControl with the ContentControl.
Just set the content of the ContentControl to the desired template and use it as ItemTemplate for the ItemsControl.