Pass data between parent child user controls - c#

Let's say I have a Book app with the following arrangement:
There are a few page types
Regular
With picture
Only picture
etc...
Each of this pages can have many bookmarks. In order to display everything correctly I have created a datatemplate selector like this
Page.xaml
<Pages:PageTypeSelector Content="{Binding}">
<Pages:PageTypeSelector.Regular>
<DataTemplate>
<Grid HorizontalAlignment="Stretch">
<!-- Show regular content here-->
<Bookmark:BookmarkView />
</Grid>
</DataTemplate>
</Pages:PageTypeSelector.Regular>
<Pages:PageTypeSelector.WithPicture>
<DataTemplate>
<Grid HorizontalAlignment="Stretch">
<!-- Show mixed content here-->
<Bookmark:BookmarkView />
</Grid>
</DataTemplate>
</Pages:PageTypeSelector.WithPicture>
<Pages:PageTypeSelector.OnlyPicture>
<DataTemplate>
<Grid HorizontalAlignment="Stretch">
<!-- Show picture content here-->
<Bookmark:BookmarkView />
</Grid>
</DataTemplate>
</Pages:PageTypeSelector.WithPicture>
</Pages:PageTypeSelector>
How the bookmark view looks like isn't important now (it just displays the bookmarks for each page)
The problem is I need somehow to set it's PageId property so that the bookmark control can get the bookmarks from a service.
I have added a property to the BookmarkView.xmal.cs
public Guid PageId
{
get { return _pageId; }
set
{
_pageId= value;
_viewModel.PageId= value; // Here I wanted to pass the same value to the
// viewModel which would doo the job then
}
}
I have then tried to do something like this
<Bookmark:BookmarkView PageId={Binding PageId, Mode=TwoWay}/>
Intellisense was suggesting PageId while I was typing but nothing is happening.
How can I pass the PageId to the view?
Edit: Changing to
public object PageId
{
get { return _pageId; }
set
{
_pageId= value;
}
}
Shows me that the value is of type System.Windows.Data.Binding. How can I now get the value?

Create a dependency property for PageID.

Related

How can I modify xaml resource at run-time?

I have many very similar resources in xaml (varying by a tiny bit: name of property in bingings, static text in header, etc.) which are quite big and complex:
<Window.Resource>
<A x:Key="a1"> ... </A>
<A x:Key="a2"> ... </A>
...
<B x:Key="b1"> ... />
<B x:Key="b2"> ... />
...
<C x:Key="c1"> ... />
...
</Window.Resource>
And my aim is to have just this:
<A x:Key="a" ... />
<B x:Key="b" ... />
<C x:Key="c" ... />
...
where resource become kind of template. But then I need to somehow define a parameter to alter each such resource (e.g. to modify property name in the binding) before using it.
My current idea is to manipulate resources as text:
var xaml = #"... Text = ""{Binding %PROPERTY%}"" ...";
xaml = xaml.Replace("%PROPERTY%", realPropertyName);
view.Content = XamlReader.Parse(xaml)
But defining xaml strings in code-behind doesn't sounds good, they should be a part of xaml, where they are used.
So I had this brilliant idea:
// get some resource and restore xaml string for it, yay!
var xaml = XamlWriter.Save(FindResource("some resource"));
But unfortunately XamlWriter is very limited, it didn't worked, the restored this way xaml is totally unusable.
Then I had a thought to define resource as string:
<clr:String x:Key="a">...</clr:String>
But multiline string and special character in xaml making this approach looking very ugly. Don't try it at home.
Ideally I want to define resources as before (to have intellisence and stuff) and just want to modify them at run-time somehow, therefore my question.
The localized case of the problem (it's quite the same) is to have parameter in DataTemplate. I was asking question about dynamic columns earlier, that's why I have so many similar resources defined currently and trying to find a solution again.
I forgot to add a concrete example of resource as well as some form of MCVE:
<Window.Resources>
<GridViewColumn x:Key="column1">
<GridViewColumn.Header>
<DataTemplate>
<TextBlock Text="Header1" />
</DataTemplate>
</GridViewColumn.Header>
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Value1}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
...
... more similar columns
...
</Window.Resources>
<ListView x:Name="listView" ItemsSource="{Binding Items}">
<ListView.View>
<GridView />
</ListView.View>
</ListView>
some columns are added
var view = (GridView)listView.View;
foreach(var column in Columns.Where(o => o.Show))
view.Columns.Add((GridViewColumn)FindResouce(column.Key));
where Columns collection defines which columns can be shown, which are hidden, their width, etc.
public class Column
{
public string Key { get; set; } // e.g. "column1"
public bool Show { get; set; }
...
}
To have 100 columns I have to define 100 "columnX" resources, but they are very similar. My challenge is to define just one and then somehow alter dynamic parts (in this case to change "Header1" to "Header2" and "Value1" to "Value2").
I have found a way to write xaml which:
has designer support
has intellisense support;
can be modified at run-time.
For this xaml (resources) needs to be put into separate ResourceDictionary which Build property set to Embedded Resource
The content will looks like this:
<ResourceDictionary ...>
<GridViewColumn x:Key="test" Header="%HEADER%"> <!-- placeholder for header -->
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding %CELL%}" /> <!-- placeholder for cell property name -->
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</ResourceDictionary>
And the code to load and modify
// get resource stream
var element = XElement.Load(Assembly.GetExecutingAssembly().GetManifestResourceStream("..."));
// get xaml as text for a specified x:Key
var xaml = element.Descendants().First(o => o.Attributes().Any(attribute => attribute.Name.LocalName == "Key")).ToString();
// dynamic part
xaml = xaml.Replace("%HEADER%", "Some header");
xaml = xaml.Replace("%CELL%", "SomePropertyName");
// xaml to object
var context = new ParserContext();
context.XmlnsDictionary.Add("", "http://schemas.microsoft.com/winfx/2006/xaml/presentation");
var column = (GridViewColumn)XamlReader.Parse(xaml, context);
view.Columns.Add(column);
I personally don't use designer at all (only to quickly navigating) and write all xaml with hands. Having no designer support is not a problem for me.
Intellisense support and seeing mistakes at compile time are very handy. Disregards of build action the xaml will be fully validated, which is a good thing (compared to saving xaml in string in code behind or in a text-file).
Separating resources from window/user control are sometimes problematic, e.g. if there are bindings with ElementName or references to other resources (which are not moved to resource dictionary), etc. I have currently issue with BindingProxy, therefore this solution is not a final one.
More focusing on your GridView example instead of the question title.
You could create a UserControl or Custom Control in order to define the appearence of a cell content.
Within the custom control, you can define your whole shared styling and define dependency properties for things that should be different per cell.
As an example, here is a custom control MyCellContent that allows to bind a Text property or to bind a MyTextPropertyName property which will automatically create a binding on the Text property, redirecting to whatever MyTextPropertyName specifies:
public class MyCellContent : Control
{
static MyCellContent()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MyCellContent), new FrameworkPropertyMetadata(typeof(MyCellContent)));
}
// Specify a property name that should be used as binding path for Text
public string MyTextPropertyName
{
get { return (string)GetValue(MyTextPropertyNameProperty); }
set { SetValue(MyTextPropertyNameProperty, value); }
}
public static readonly DependencyProperty MyTextPropertyNameProperty =
DependencyProperty.Register("MyTextPropertyName", typeof(string), typeof(MyCellContent), new PropertyMetadata(null, OnTextPropertyChanged));
private static void OnTextPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
BindingOperations.SetBinding(d, TextProperty, new Binding(e.NewValue as string));
}
// The text to be displayed
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(MyCellContent), new PropertyMetadata(null));
}
And in Themes/Generic.xaml
<Style TargetType="{x:Type local:MyCellContent}">
<!-- Demonstrate the power of custom styling -->
<Setter Property="Background" Value="Yellow"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:MyCellContent}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<TextBlock Text="{TemplateBinding Text}" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Now, this is something to build upon.
For example, you could create a CellTemplateSelector that creates a cell template, where the Text property binding is determined by another property value of the data item:
// Specialized template selector for MyGenericData items
public class GridViewColumnCellTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
var data = item as MyGenericData;
return CreateCellTemplate(data.MyTargetPropertyName);
}
/// <summary>
/// Create a template with specified binding path
/// </summary>
private DataTemplate CreateCellTemplate(string targetPropertyName)
{
FrameworkElementFactory myCellContentFactory = new FrameworkElementFactory(typeof(MyCellContent));
myCellContentFactory.SetValue(MyCellContent.MyTextPropertyNameProperty, targetPropertyName);
return new DataTemplate
{
VisualTree = myCellContentFactory
};
}
}
Another different way to use the MyCellContent control would be a customized MyGridviewColumn, that does basically the same as the template selector above, but instead of a data driven property selection, it allows to specify a binding to be used on the Text property:
/// <summary>
/// Pre-Templated version of the GridViewColumn
/// </summary>
public class MyGridviewColumn : GridViewColumn
{
private BindingBase _textBinding;
public BindingBase TextBinding
{
get { return _textBinding; }
set
{
if (_textBinding != value)
{
_textBinding = value;
CellTemplate = CreateCellTemplate(value);
}
}
}
/// <summary>
/// Create a template with specified binding
/// </summary>
private DataTemplate CreateCellTemplate(BindingBase contentBinding)
{
FrameworkElementFactory myCellContentFactory = new FrameworkElementFactory(typeof(MyCellContent));
myCellContentFactory.SetBinding(MyCellContent.TextProperty, contentBinding);
return new DataTemplate
{
VisualTree = myCellContentFactory
};
}
}
Usage example with some test data:
<Window.Resources>
<x:Array x:Key="testItems" Type="{x:Type local:MyGenericData}">
<local:MyGenericData Property1="Value 1" Property2="Value 3" MyTargetPropertyName="Property1"/>
<local:MyGenericData Property1="Value 2" Property2="Value 4" MyTargetPropertyName="Property2"/>
</x:Array>
<local:GridViewColumnCellTemplateSelector x:Key="cellTemplateSelector"/>
</Window.Resources>
...
<ListView ItemsSource="{Binding Source={StaticResource testItems}}">
<ListView.View>
<GridView>
<GridViewColumn CellTemplateSelector="{StaticResource cellTemplateSelector}" Header="ABC" Width="100" />
<local:MyGridviewColumn TextBinding="{Binding Property2}" Header="DEF" Width="100" />
</GridView>
</ListView.View>
</ListView>
The result:
A gridview where the first column displays the values "Value 1" and "Value 4", because it selects the value from "Property1" in the first row and from "Property2" in the second row. So the displayed data is driven by two data dimensions: the specified property name and the target property value.
The second column displays the values "Value 3" and "Value 4", because it utilizes the specified binding expression "{Binding Property2}". So the displayed data is driven by the specified binding expression, which could refer to a data property or anything else that's legally binding within a data grid cell.

How do I bind a property to my xaml that I manipulate before setting the property?

I have this method:
private async Task DisplayVideos(string query)
{
var videoObj = await _mediaService.GetVideos(query);
Videos = videoObj.Hits;
var size = "640x360";
var picIdList = videoObj.Hits.Select(x=>x.Picture_id).ToList();
foreach (var pic in picIdList)
{
PictureId = $"https://i.vimeocdn.com/video/{pic}_{size}.jpg";
}
}
This method hits an api endpoint and gets back a video object. I want to get the picture_id from the video object and manually set a size, then input the parameters into a specific url and then set the binding context of my image in my xaml to that specific url as I'm doing above.
However, the issue above is I'm setting PictureId to the last url in that list. How can I fix it so that a list of picture ids and in my listview, I have a image bind for every cell. I'm using FlowListView layout.
Here's my xaml:
<flv:FlowListView FlowColumnCount="2" SeparatorVisibility="None" HasUnevenRows="true"
FlowItemTappedCommand="{Binding ItemTappedCommand}"
FlowItemsSource="{Binding Videos}" >
<flv:FlowListView.FlowColumnTemplate>
<DataTemplate>
<Image Aspect="AspectFill" HeightRequest="200" Source="{Binding PictureId}" />
</DataTemplate>
</flv:FlowListView.FlowColumnTemplate>
</flv:FlowListView>
Let's assume that GetVideos() returns a List<Video>, and that Video is a class that you've defined in your code, so you can modify it.
Just add a read-only Property to the Video class that will return the url for the image
public string PictureUrl {
get {
$"https://i.vimeocdn.com/video/{Picture_Id}_640x360.jpg";
}
}

UWP Grid inside Datatemplate

I have a Pivot where I set the header in my Pivot.HeaderTemplate it is basically just showing Names of Books. In my Pivot.ItemTemplate I want to show a Grid which is build in my .xaml.cs but since the Grid is in my DataTemplate I can not access the Grid x:Name anymore in the code behind in .xaml.cs. books is a Collection of Books which contains a Name and a Title
MainPage.xaml
<Pivot ItemsSource="{x:Bind books}">
<Pivot.HeaderTemplate>
<DataTemplate x:DataType="local:Book">
<TextBlock Text="{x:Bind Name}"/>
</DataTemplate>
</Pivot.HeaderTemplate>
<Pivot.ItemTemplate>
<DataTemplate>
<Grid
x:Name="BooksGrid"
BorderBrush="Black" BorderThickness="1 1 0 0"
Margin="0 10 0 0>
</Grid>
</DataTemplate>
</Pivot.ItemTemplate>
Now I want to acces BooksGrid iny the code behind and actually create the Grid
MainPage.xaml.cs
public MainPage()
{
this.InitializeComponent();
}
private void DrawGrid()
{
//create columns of Grid
for (int i = 0; i < booksize.XProperties.Count + 1; i++)
{
BooksGrid.ColumnDefinitions.Add(new ColumnDefinition
{
});
}
BooksGrid.ColumnDefinitions[0].Width = GridLength.Auto;
}
....
Already here at BooksGrid.ColumnDefinitions.Add(...) I get the error that BooksGrid can not be found.
My DrawGrid works if I do not place the Grid definition in my DataTemplate and also outside myPivot. So the MainPage.xaml.csdoes not find it when the Grid is inside my DataTemplate
I've read that the solution might be that I have to acces the Grid instance that I want to work with, as soon as the DataTemplate gets loaded. But I do not know how to do that either.
EDIT PART to first solution:
I'm also using BooksGrid in another method
MainPage.xaml.cs
private void DrawBooksFront(Front front)
{
int row;
int column;
column = booksize.XProperties.IndexOf(front.CustomProps[booksize.XLabel])+1;
row = booksize.YProperties.IndexOf(front.CustomProps[booksize.YLabel])+1;
Frame newFrame = new Frame();
TaskBoardGrid.Children.Add(newFrame);
Grid.SetColumn(newFrame, column);
Grid.SetRow(newFrame, row);
}
The reason you cannot access your BooksGrid is because it will be dynamically generated for each book in the books collection. So for every book a Grid will be generated.
OPTION 1:
You can add a Loaded event to your grid:
<Pivot x:Name="Pivot" ItemsSource="{x:Bind books}">
<Pivot.HeaderTemplate>
<DataTemplate x:DataType="local:Book">
<TextBlock Text="{x:Bind Name}"/>
</DataTemplate>
</Pivot.HeaderTemplate>
<Pivot.ItemTemplate>
<DataTemplate>
<Grid
BorderBrush="Black" BorderThickness="1,1,0,0"
Margin="0,10,0,0" Loaded="DrawGrid">
</Grid>
</DataTemplate>
</Pivot.ItemTemplate>
and in your code behind:
private void DrawGrid(object sender, RoutedEventArgs e)
{
Grid grid = sender as Grid;
// Load your grid..
}
EDIT - OPTION 2:
If you'd like to access your grids from code behind in a different way (like suggested in your edit) you can always do the following:
private void DrawBooksFront(Front front)
{
// Loop through the pivot's items and get the content from each item's ContentTemplate.
foreach (var item in Pivot.Items)
{
PivotItem pivotItem = Pivot.ContainerFromItem(item) as PivotItem;
Grid grid = pivotItem.ContentTemplate.LoadContent() as Grid;
// Do something with the grid.
}
}
If your goal is to display previews of the pages of the book inside a PivotItem in a grid-like manner [picture below], then you're better off placing GridView in a DataTemplate of Pivot.ItemTemplate and using data binding to display those pages automatically, this would eliminate the need to write the code in xaml.cs that you showed.
Please, share more details about your app (what you're given and what the end result should look like) so we could help you better.

Block user navigation in a Pivot item for UWP apps

How can you block a user from clicking on a Pivot item in a UWP XAML app but still show the heading for each pivot item? For example, I have three pivot items that are labeled "Step 1", "Step 2", and "Step 3". I want each of those items to show up at the top of the pivot table, but not be user engagable. They are only there to proivde awarness to the users current location in a process.
I have tried IsLocked="true" in the definition of the pivot table, but it only shows me the title for the pivot item I am currently on.
I tried IsEnabled="false" and that didn't work. Then I tried data binding to a property and used the setter to restrict its value, and that did work.
View:
<Pivot SelectedIndex="{x:Bind PageViewModel.MyPivotIndex, Mode=TwoWay}">
<PivotItem Header="Item1">
<TextBlock Text="Stuff1"/>
</PivotItem>
<PivotItem Header="Item2" IsEnabled="False">
<TextBlock Text="Stuff2"/>
</PivotItem>
</Pivot>
ViewModel:
private int _myPivotIndex;
public int MyPivotIndex
{
get
{
return _myPivotIndex;
}
set
{
if (ConditionMet)
{
_myPivotIndex = value;
}
else
{
_myPivotIndex = 0;
OnPropertyChanged("MyPivotIndex");
}
}
}
A non-MVVM option that should work is if you used the "SelectionChanged" event in the code-behind to check a condition and then if need be set it back to Item1 by setting SelectedItem or SelectedIndex.
If you want the "disabled" items to not highlight on mouse-over, you'll have to copy the style (https://msdn.microsoft.com/en-us/library/windows/apps/mt299144.aspx) and modify it in the the "PointerOver" Visual State.

Change GridView Item DataTemplate based on seperate ViewModel Property in WinRT

What I am attempting to do is have a collection of items shown in a GridView control and have the size of these items change based on a command executed by a separate button.
For example, having a row of buttons across the top reading “Small”, “Medium” and “Large” and having the items in the GridView respond to the relevant command by displaying its items in the relevant state.
I have the gridview declared like so
<GridView ItemsSource="{Binding Squares}"
With Squares being an observable collection of Square objects that have a Title and a Fill property.
At first I went down the DataTemplateSelector route by declaring the following data templates in the Resources section of the page.
<DataTemplate x:Key="SquareSmallTemplate">
<Grid Height="100" Width="100">
<Rectangle Fill="{Binding Fill}"/>
<TextBlock Text="{Binding Title}"/>
</Grid>
</DataTemplate>
<DataTemplate x:Key="SquareMediumTemplate">
<Grid Height="150" Width="150">
<Rectangle Fill="{Binding Fill}"/>
<TextBlock Text="{Binding Title}"/>
</Grid>
</DataTemplate>
<DataTemplate x:Key="SquareLargeTemplate">
<Grid Height="200" Width="200">
<Rectangle Fill="{Binding Fill}"/>
<TextBlock Text="{Binding Title}"/>
</Grid>
</DataTemplate>
The idea being that the grid’s height and width properties are different for the relevant template. I declared the following data templates in the selector
public DataTemplate SmallTemplate { get; set; }
public DataTemplate MediumTemplate { get; set; }
public DataTemplate LargeTemplate { get; set; }
And in the SelecteTemplateCore method I just returned the relevant template
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
{
string value = item as string;
if (value != null)
{
if (value == "Small")
return SmallTemplate;
else if (value == "Medium")
return MediumTemplate;
else if (value == "Large")
return LargeTemplate;
return base.SelectTemplate(item, container);
}
else
{
return base.SelectTemplateCore(item, container);
}
}
However, with this method (and, by design of the DataTemplateSelector) the object being passed in is the item in the collection (the Square).
This is fine if I wanted each item to have a different appearance or something, but what I need is the template to change based on another property on the view model.
For this, I have the following
public string State {get; set;}
and this is set to “Small”, “Medium, or “Large based on a separate row of three buttons that execute a command that sets this property to the relevant value.
How do I relate the State property to changing to the relevant DataTemplate?
Another route I tried was to have a single Data template that used the VSM to animate the Height/Width properties in the relevant states. However I could not get the relevant animation to execute when the State changed.
Any help would be great, thanks
There are a few ways to do this, I'm not sure which would be best. In any case, you'll need 1) a trigger, and 2) the action to update the template. I am leaning towards using PropertyChangedTrigger along with an InvokeCommandAction.
<GridView x:Name="grid">
<i:Interaction.Triggers>
<ei:PropertyChangedTrigger Binding="{Binding State}">
<i:InvokeCommandAction Command="{Binding RelativeSource={RelativeSource AncestorType=UserControl},Path=UpdateTemplateCommand}" CommandParameter="{Binding State}" />
</ei:PropertyChangedTrigger>
</i:Interaction.Triggers>
<GridView>
(Here the "AncestorType" would just be the root of the view, so please change "UserControl" as needed.)
Then in the view, you would have an ICommand that updates the template:
UpdateTemplateCommand = new DelegateCommand(state => {
switch ((string)state)
{
default:
case "Small" : grid.ItemTemplate = "SquareSmallTemplate"; break;
case "Medium" : grid.ItemTemplate = "SquareMediumTemplate"; break;
case "Large" : grid.ItemTemplate = "SquareLargeTemplate"; break;
}
});
IDK ... after writing this out it seems a bit convoluted. Maybe you'd find it preferable to add a CurrentDataTemplate property to the view-model, and assign it by creating DataTemplates from strings using XamlReader.

Categories

Resources