Say I have a simple ItemTemplate for a ListView
<ListView ItemsSource="{x:Bind ListItems}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:TextItem">
<TextBlock Text="{x:Bind Item}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
MainPage.xaml
When I run the app, I get a ListView populated with TextBlocks - one for each item in ListItems (which is a behind defined variable). However, in the XAML designer, I see nothing.
So, is there a way to preview the ItemTemplate/DataTemplate in the XAML designer, with a set number of placeholder TextBlocks with placeholder text replacing the Text="{x:Bind Item}"? Or just preview a single TextBlock?
This is not a duplicate of
Design View of a DataTemplate in a ResourceDictionary in Visual Studio - because I can't use Blend. Everything I've looked up that says use Blend in a certain way merely gives me the message 'This feature is not available for projects targeting "Windows 10 Fall Creators Update (10.0; Build 16299)"'
Can I preview my DataTemplate(s) in my collection-based controls in the designer? - well, perhaps it is, but I don't understand the accepted answer, which is now several years old. An answer containing an example tailored to my question would be really helpful.
Microsoft actually posted a (semi helpful) tutorial on this in April of this year: link
Requirements
Whatever you want to display, you will need this at the top of your XAML code (should be there by default if you created the page using VS's templates):
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
A simple list of strings
For something simple like a list of strings, something like this will work just fine (taken from the above mentioned MSDN page):
<ListView>
<d:ListView.Items>
<system:String>Item One</system:String>
<system:String>Item Two</system:String>
<system:String>Item Three</system:String>
</d:ListView.Items>
</ListView>
Display something more complex
If you want to use a data source from somewhere else in your code, it gets a little more ugly:
First you need to set your data context (usually done at the page level, but you can also add it just on the element where you want to use it):
d:DataContext="{d:DesignInstance
yournamespace:YourDataSourceClass,
IsDesignTimeCreatable=True}"
And on the ListView add:
d:ItemsSource="{Binding data}"
If you are using an ItemTemplate, you will need to add "d:" variants for everything you want to pull from your data source.
Full example code:
XAML:
<Page
x:Class="exampleApp.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:exampleApp"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance local:DesignTimeData,
IsDesignTimeCreatable=True}"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid>
<Grid>
<ListView ItemsSource="{x:Bind ListItems}" d:ItemsSource="{Binding DummyData}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:TextItem">
<TextBlock Text="{x:Bind Item}" d:Text="{Binding Item}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</Grid>
</Page>
C#:
using System.Collections.ObjectModel;
using Windows.UI.Xaml.Controls;
namespace exampleApp {
public sealed partial class MainPage : Page {
public ObservableCollection<TextItem> ListItems;
public MainPage() {
this.InitializeComponent();
//populate you ListItems list
}
}
public class TextItem {
public string Item { get; set; }
}
public class DesignTimeData {
public ObservableCollection<TextItem> DummyData { get; set; } = new ObservableCollection<TextItem> {
new TextItem { Item = "foo" },
new TextItem { Item = "bar" },
new TextItem { Item = "bla" },
new TextItem { Item = "blubb" }
};
}
}
Remarks
It seems you have to use "Binding" rather than "x:Bind" for the design-time bindings. From what I understand, x:Bind works using code generated at compile time, which is not executed by the XAML designer
When you change your Binding declarations or data source code, you will have to recompile to see the changes in the designer. Other design changes will reflect in real time
Binding is more limited than x:Bind in some regards (notably you cannot bind to class methods using Binding, only x:Bind). Keep that in mind when writing your design-time data
Sadly some of the more convenient ways of getting design-time data into your app are WPF and Xamarin only (Blends' data window, data declared in XAML file, and especially auto-generated sample data)
(Everything was tested using Visual Studio 2019 version 16.10.3.)
Related
I am working on a Xamarin App. I am making a custom control with a Label control, a DatePicker control and an Entry control. I had to create quite a few BindableProperties for the Date Control in the Custom Control such as MaximumDate, MinimumDate Property among many other properties. As far as I understand, the reason I have to create these BindableProperty members in my Custom control are because I have no access to the properties of the child controls when the custom control is used on a view. Is there a way to access the properties of the child control that is embedded in a custom control? I could save a lot of lines of code defining the BindableProperties and their CLR properties and other things.
Here is my Custom control XAML (I have removed all the elements except for a Label element in the posted code to make the code more readable and for brevity.
<StackLayout xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="DPSReminders.Controls.CustomLabel"
xmlns:controls="clr-namespace:DPSReminders.Controls"
xmlns:sfLayout="clr-namespace:Syncfusion.XForms.TextInputLayout;assembly=Syncfusion.Core.XForms"
xmlns:sfPicker="clr-namespace:Syncfusion.XForms.Pickers;assembly=Syncfusion.SfPicker.XForms"
xmlns:xct="http://xamarin.com/schemas/2020/toolkit"
xmlns:fai="clr-namespace:FontAwesome">
<StackLayout.Resources>
<ResourceDictionary>
</ResourceDictionary>
</StackLayout.Resources>
<StackLayout Orientation="Horizontal" Margin="10">
<Label x:Name="myLabel"
Text=""
FontFamily="FASolid"
VerticalOptions="Center"
HorizontalOptions="Start"
Margin="10">
</Label>
</StackLayout>
The code behind file:
public class CustomLabel : StackLayout
{
public static readonly BindableProperty LabelTextProperty =
BindableProperty.Create(nameof(LabelText), typeof(string), typeof(CustomLabel),
defaultBindingMode: BindingMode.TwoWay,
propertyChanged: LabelTextPropertyChanged);
public string LabelText
{
get => GetValue(LabelTextProperty)?.ToString();
set => SetValue(LabelTextProperty, value);
}
private static void LabelTextPropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
var control = bindable as CustomLabel;
control.myLabel.Text = newValue?.ToString();
}
public CustomLabel()
{
InitializeComponent();
}
}
Here is the page where I am trying to use the custom control.
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="DPSReminders.Views.DateTimeTest"
xmlns:fai="clr-namespace:FontAwesome"
xmlns:vm="clr-namespace:DPSReminders.ViewModels"
xmlns:controls="clr-namespace:DPSReminders.Controls"
xmlns:xct="http://xamarin.com/schemas/2020/toolkit"
>
<controls:CustomLabel LabelText = "{Binding MyLabelText}"/>
</ContentPage>
I am wondering if I could do a line like this in my label, that would make my life much easier.
<controls:CustomLabel:myLabel.Text = "{Binding MyLabelText}"/>
Then, I can remove all the code creating the BindableProperties and the supporting CLR properties etc. when a built-in bindable property for the same purpose is already available in the child control. Is this something we can do?
Try using template instead.
Xamarin.Forms control templates enable you to define the visual structure of ContentView derived custom controls, and ContentPage derived pages. Control templates separate the user interface (UI) for a custom control, or page, from the logic that implements the control or page. Additional content can also be inserted into the templated custom control, or templated page, at a pre-defined location.
Doc link:
https://learn.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/templates/control-template
I have a Xamarin forms app where I exhibit the result from different sensors all in one ListView. Every sensor populates a 'DataTemplate' ViewCell where I show the name, location and others and, in the central grid the values.
The point is that the visualization for those values should be different from each sensor (imagine I want a moving arrow for the wind, a growing blue box for the collected water, a number for the temperature, and so on)
Is it possible to return a custom UIElement, or a Grid or something from a IValueConverter and be able to do this task ?
If not, what would you recommend ?
PS: I think that, what I want to do, is done via ContentPresenter. but .. I cannot find proper details on how to achieve it.
Sounds like DataTemplateSelector should solve your issue:
A DataTemplateSelector can be used to choose a DataTemplate at runtime
based on the value of a data-bound property. This enables multiple
DataTemplates to be applied to the same type of object, to customize
the appearance of particular objects. This article demonstrates how to
create and consume a DataTemplateSelector.
Official docs: Creating a Xamarin.Forms DataTemplateSelector
Creating a DataTemplateSelector:
public class PersonDataTemplateSelector : DataTemplateSelector
{
public DataTemplate ValidTemplate { get; set; }
public DataTemplate InvalidTemplate { get; set; }
protected override DataTemplate OnSelectTemplate (object item, BindableObject container)
{
return ((Person)item).DateOfBirth.Year >= 1980 ? ValidTemplate : InvalidTemplate;
}
}
Consuming a DataTemplateSelector in XAML:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:local="clr-namespace:Selector;assembly=Selector" x:Class="Selector.HomePage">
<ContentPage.Resources>
<ResourceDictionary>
<DataTemplate x:Key="validPersonTemplate">
<ViewCell>
...
</ViewCell>
</DataTemplate>
<DataTemplate x:Key="invalidPersonTemplate">
<ViewCell>
...
</ViewCell>
</DataTemplate>
<local:PersonDataTemplateSelector x:Key="personDataTemplateSelector"
ValidTemplate="{StaticResource validPersonTemplate}"
InvalidTemplate="{StaticResource invalidPersonTemplate}" />
</ResourceDictionary>
</ContentPage.Resources>
...
</ContentPage>
<ListView x:Name="listView" ItemTemplate="{StaticResource personDataTemplateSelector}" />
The ListView of my XAML file is being filled with a ViewModel that has an ObservableCollection from a service but the ListView is not showing the information. I already check that the service is returning the correct information.
This is the code of my XML file :
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="XAMLUnit1.Views.NetflixRoulettePage" Title="Movie">
<ContentPage.Content>
<StackLayout>
<AbsoluteLayout>
<BoxView Color="Gray" AbsoluteLayout.LayoutFlags="All" AbsoluteLayout.LayoutBounds="0,0,1,1"></BoxView>
<SearchBar x:Name="searchBar"
Placeholder="Search By Actor's name"
PlaceholderColor="White"
AbsoluteLayout.LayoutFlags="All"
AbsoluteLayout.LayoutBounds="0.1,0.1,1,1" SearchCommand="{Binding SearchMovieCommand}" ></SearchBar>
</AbsoluteLayout>
<ListView x:Name="ListOfMovies"
ItemsSource="{ Binding MovieList}" >
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout>
<ImageCell
ImageSource="{Binding poster_path, StringFormat='https://image.tmdb.org/t/p/w500{0}'}">
</ImageCell>
<StackLayout Orientation="Horizontal">
<TextCell Detail="{Binding title}"></TextCell>
<TextCell Detail="{Binding release_date}"></TextCell>
</StackLayout>
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage.Content>
</ContentPage>
This is the ViewModel that calls the service and it's uses its ObservableCollection as ItemsSource for the ListView :
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
using Xamarin.Forms;
using XAMLUnit1.Models;
using XAMLUnit1.ServiceImpl;
using XAMLUnit1.Services;
namespace XAMLUnit1.ViewModels
{
public class MovieRouletteViewModel
{
IMovieService service;
public ObservableCollection<Movie> MovieList { get; set; }
public ICommand SearchMovieCommand { get; set; }
public MovieRouletteViewModel()
{
service = new MovieServiceFinder();
SearchMovieCommand = new Command(GetMovies);
}
private void GetMovies()
{ var list= service.GetMovies("");
MovieList = list;
}
}
}
public partial class NetflixRoulettePage : ContentPage
{
MovieRouletteViewModel viewModel;
public NetflixRoulettePage()
{
InitializeComponent();
viewModel = new MovieRouletteViewModel();
BindingContext = viewModel;
}
private void SearchBar_TextChanged(object sender, TextChangedEventArgs e)
{
}
}
Do not set the ObservableCollection to a new List it will break the binding. Clear the items from the list and add the new items to it.
public MovieRouletteViewModel()
{
service = new MovieServiceFinder();
SearchMovieCommand = new Command(GetMovies);
MovieList = new ObservableCollection<Movie>();
}
private void GetMovies()
{
var list= service.GetMovies("");
MovieList.Clear();
foreach(Movie movie in list)
{
MovieList.Add(movie);
}
}
There is a few things i want to note on this problem, though mainly as the comments rightly illuminate, replacing the collection with a new collection breaks the bindings. hence why its not updating.
However there are several solutions to consider.
Implement INotifyPropertyChanged on the collection and replace the whole collection like you are doing, This will work fine. However it does resets the scroll and basically recalculates everything from scratch. Some would say this defeats the point of an ObservableCollection as they have their own built in notification plumbing, Yeah it does. Though, if you are replacing the whole Collection with a new collection then you are going to save on oodles of Cyclic calculations when compared to cleaRing and adding each item back individually Which will basically fire for every update
Call Clear and Add on each item individually.. If the collection hasn't changed much, you can even take it a step further by just comparing the 2 collections and updating whats needed. However once again, if the collections are dissimilar, then this approach is still expensive, and on a mobile device you want to minimize screen recalculations where ever possible .
Create a subclassed collection, implement your own replace/update, and Add/Remove range methods, and INotifyPropertyChanged giving you the best of both worlds, this will allow atomic modification of the collection and then you can fire property changed event once.
There are many examples of all these approaches online, its just worth noting clearing and adding items sometimes is not the best approach for mobile devices. It depends how big your collections are, how much is changing and why.
If it is a completely different list, then replacing the collection is fine in my opinion.
If its the same list, then well you may want to compare and modify to save on property changes
Anyway good luck
I disagree with other answers. You can set the Movies as many times as you want, this is not the problem.
The problem is just that your viewmodel doesn't implement INotifyPropertyChanged.
Your UI is just not notified when you set your ObservableCollection.
Your service command is actually replacing the ObservableCollection, you need to change your GetMovies() method to
var list = service.GetMovies("");
MovieList.clear();
foreach(Movie m in list)
{ MovieList.Add(m);}
I'm relatively new to UWP being a developer used to using Windows forms.
In windows forms what I'm attempting to do I would do quite easily through form inheritance.
UWP seems to have no form of layout inheritance, it appears that you have to design a UserControl to achieve this and then place it on the page and fill the page with the control.
I have a control which contains a 2 column grid. In the first column I have placed a StackPanel and at the moment the second column is empty.
<UserControl x:Name="userControl"
x:Class="App1.BaseLayoutControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:App1"
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>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="11*"/>
<ColumnDefinition Width="29*"/>
</Grid.ColumnDefinitions>
<StackPanel Margin="0" Background="#FF0B1E70"/>
</Grid>
</UserControl>
And the code:
using System.ComponentModel;
using System.Runtime.CompilerServices;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using App1.Annotations;
// The User Control item template is documented at http://go.microsoft.com/fwlink/?LinkId=234236
namespace App1
{
public sealed partial class BaseLayoutControl : INotifyPropertyChanged
{
public BaseLayoutControl()
{
InitializeComponent();
}
public StackPanel LeftStackPanel
{
get { return ( StackPanel ) GetValue( LeftStackPanelProperty ); }
set
{
SetValue( LeftStackPanelProperty, value );
OnPropertyChanged( nameof( LeftStackPanel ) );
}
}
// Using a DependencyProperty as the backing store for MyProperty. This enables animation, styling, binding, etc...
public static readonly DependencyProperty LeftStackPanelProperty =
DependencyProperty.Register( "LeftStackPanel", typeof( StackPanel ), typeof( BaseLayoutControl ), null );
public event PropertyChangedEventHandler PropertyChanged;
[ NotifyPropertyChangedInvocator ]
private void OnPropertyChanged( [ CallerMemberName ] string propertyName = null )
{
PropertyChanged?.Invoke( this, new PropertyChangedEventArgs( propertyName ) );
}
}
}
Now what I want to do is expose the StackPanel as a property "LeftStackPanel" and also at design time where I have placed the control on the page I would like to be able to drag and drop Buttons, CheckBoxes, Text etc into the StackPanel inside the control. Also I would like to expose the second unused grid column so that items can also be place in it.
All the examples I can find seem to only deal with simple object types like ints and strings.
Any help appreciated.
Terry
This is a longtime challenge when migrating from WinForms to XAML.
The short answer is that you should be looking at creating a CustomControl and not a UserControl.
A UserControl is a self-contained UI element that you provide data and it displays it.
When you need to customize not only the information displayed but also the way you display it, you need to use a CustomControl. That will enable the scenarios that you are trying to achieve in your case.
Here is a very good visual tutorial on how to author your first CustomControl. Also, for mode details on the differences between User and Custom control check this post. In fact there are many articles and tutorials out there on this topic. Most of them are from the WPF days, but everything still applies in UWP.
This has to be simple, at least it was in good old .Net where it took maybe four lines of code. I'm working in VS2010, C#, WPF4.
I have a user control with a textbox. When I click a button in the main window, I want my user control textbox to reflect some text. Is this possible in WPF4 with less than 500 lines of esoteric code?
The problem is that while I know the textbox is getting the new text as evidenced from breakpoints in the user control code, that text is never being reflected to the main window. The main window still shows the original text. It has to be some kind of binding thing, and I really don't think I should have to create templates and resources and all for this simple situation. It's got to be something simple that I'm forgetting in the forest of WPF4. Below is what I have. After clicking the button, the textbox is still blank; it does not say "hello earthlings."
In the user control code:
public partial class UserControl1 : UserControl
{
public static readonly DependencyProperty TextProperty;
public UserControl1()
{
InitializeComponent();
}
static UserControl1()
{
TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(UserControl1), new UIPropertyMetadata(null));
}
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
}
User control xaml:
<UserControl x:Class="WTFUserControlLibrary.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">
<Grid Height="164" Width="220">
<TextBox Name="txtTest" BorderBrush="red" BorderThickness="2" Height="25" Text="{Binding ElementName=UserControl1, Path=Text, Mode=TwoWay}"></TextBox>
</Grid>
(I have no idea what the text binding is supposed to be doing in this case.)
Main window code:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void button1_Click(object sender, RoutedEventArgs e)
{
WTFUserControlLibrary.UserControl1 uc = new WTFUserControlLibrary.UserControl1();
uc.Text = "hello earthlings";
}
}
and the main window xaml:
<Window x:Class="WpfApplication2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:my="clr-namespace:WTFUserControlLibrary;assembly=WTFUserControlLibrary"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Button Content="Button" Height="23" HorizontalAlignment="Left" Margin="71,65,0,0" Name="button1" VerticalAlignment="Top" Width="75" Click="button1_Click" />
<my:UserControl1 HorizontalAlignment="Left" Margin="71,94,0,0" Name="userControl11" VerticalAlignment="Top" Height="116" Width="244" />
</Grid>
Thanks Earthlings (and also those who designed this mess!)
In your method button1_Click you are creating a new user control. This is not the usercontrol in the window and is never displayed.
Instead, give your usercontrol a name in the XAML:
x:Name="uc"
Then in the button1_Click method you just remove that first line where you create a new usercontrol.
update
You want the user control XAML to look more like this:
<UserControl x:Class="WTFUserControlLibrary.UserControl1"
x:Name="thisControl"
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">
<Grid Height="164" Width="220">
<TextBox Name="txtTest" BorderBrush="red"
BorderThickness="2" Height="25"
Text="{Binding ElementName=thisControl, Path=Text, Mode=TwoWay}" />
</Grid>
I added x:Name="thisControl" to the root UserControl element, and then referenced this in the binding.
I'll try to explain the binding:
You have this textbox inside your user control, but you want to be able to bind the text value to something outside the user control. What you've done is set up a dependency property on the user control, and bound that textbox to it, so you are using the binding infrastructure pass values from the top level of the UserControl to constituent controls inside it.
Basically, it looks like this:
data
---> bind UserControl1.Text to data
---> bind TextBox.Text to UserControl1.Text