Variable Binding-Path - wpf

I need to display a memory dump for a technical application. Each Byte (Cell) should be defined via a DataTemplate to show additional information (highlight via setting Background color, individual Tooltip etc). I made the following attempt:
<DataTemplate x:Key="HexNumberTemplate">
<Grid>
[...]
<TextBlock>
<TextBlock.Text>
<Binding Path="Cell[0].Value">
<Binding.Converter>
[...]
</Binding.Converter>
</Binding>
</TextBlock.Text>
</TextBlock>
</Grid>
</DataTemplate>
The final result should look like this:
My problem is the fix coded Binding path. 'Cell' is a list of objects that holds all necessary information to display the cell. Using this approach, I need to define 16 times the same DataTemplate with Cell[0] to Cell[15]. I definitely want to avoid this!
I read an approach defining the DataTemplate in source code where I assemble the XAML in a string and call Markup.XamlReader.Load(MemoryStreamOfTheString). But here I lose the comfort of the Visual Studio IDE.
Is it possible to define the DataTemplate in XAML and make the indexer of the Cell-Object a parameter?

You should do like you have read: create templates dynamically, by loading them with XamlReader. In order to have comfort of XAML editor, you can define your template in a separate xaml file like this:
<DataTemplate
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid DataContext="{Binding Current_Cell}">
<!--Your template controls goes here.-->
</Grid>
</DataTemplate>
Then set type of this file to Resource, and load it into string and simply replace Current_Cell with each individual cell numbers before you load template from string when you construct your view.
By setting DataContext of Grid you help yourself to use other binding inside the template (context is already set to the current cell, and you don't need to replace it everywhere).
I was in a the same situation recently, only difference was, that my grid had totally dynamic columns (loaded from server), so I didn't even have the opportunity to create 16 templates :)

Try it with ListBoxes.
The outer ListBox includes the rows which are ListBoxes too, each of them binded to a List object. And you can create the DataTemplate of the ListBoxItems.
<DataTemplate x:Key="innerListBoxItem">
[...]
<TextBlock Text="{Binding Value}" />
[...]
<DataTemplate>
<DataTemplate x:key="outerListBoxItem">
<Grid>
<ListBox ItemTemplate="{StaticResource innerListBoxItem}" ItemCollection="{Binding Cells}"/>
</Grid>
<DataTemplate>
and whereever you want to put this control:
<ListBox ItemTemplate="{StaticResource outerListBoxItem}" ItemCollection={Binding CellsList}"/>
code behind:
public class ListOfCells { public List<Cell> Cells {get; set; } }
public List<ListOfCells> CellsList {get; private set; }

You can try and use the Attached Behavior pattern. You can bind an attached property to the column number, and the attached behavior will bind the text to the required cell given the column number.

I would suggest to use a single column DataGrid with custom Header and Cell templates.
You grid won't benefit from indivudual cells resizing, will it? Your header is going to have a fixed number of columns, you cell template can be implemented as a subclass of ListControl - we just need to change StackPanel's orientation from vertical to horizontal. Then, your bound object will be a collection of bytes, which is easy as your cell control is derived from ListControl.
Please let us know if that makes sense.

Related

connect View to ViewModel with DataTemplate

I'm trying to understand. When I'm connecting View to ViewModel like this:
<DataTemplate DataType="{x:Type local:MyViewModel}">
<local:MyView />
</DataTemplate>
What does it mean?
It looks like the View is set to be the DataTemplate of the ViewModel. BUT the ViewModel doesn't have a Property of DataTemplate. So what exactly is going on in there?
A demonstration of the question - How do I do that by code (Connecting the View and ViewModel this specific way. I can't write ViewModel.DataTemplate = View)?
Thank you.
It means "To whatever control whose Content data is MyViewModel place MyView there". You are not setting DataTemplate of viewmodel (That does not mean anything) you are setting the DataTemplate for the control whose Data is MyViewModel.
Take for example an ItemsControl with an Items Source of
ObservableCollection<Employee> Employees
where each Employee is represented by a DataTemplate for Example :
<DataTemplate TargetType="{x:Type local:Employee}">
<StackPanel>
<TextBlock Text="{Binding FirstName}" />
<TextBlock Text="{Binding LastName}" />
</StackPanel>
</DataTemplate>
So in the same manner a MyViewModel.cs such as Employee.cs as a visual representation based on a DataTemplate .
and represented for example as such :
<ContentControl Content="{Binding MyViewModelProperty}" />
The way how it works is very simple. Your definition of DataTemplate is something like a definition of how a data will look like. In your example the data that you want to represent visually are of type:
DataType="{x:Type local:MyViewModel}"
By defining DataTemplate in control, window or other resources, e.g.
<UserControl.Resources> ...your template... <UserControl.Resources>,
you say "Hey, I want that all my data of type local:MyViewModel will look like this...". Inside the template you define a root control, that will be put in all places where your local:MyViewModel have been used. Normally when you place local:MyViewModel in Grid, ContentControl or other containers, you will see its string representation like "xxxx.xxxxx.MyViewModel" instead of visual.
To to see a graphical representation you must define a DataTemplate. It will replace the string "xxxx.xxxxx.MyViewModel" - representing your data and put there a visual control you defined in the template. Then when it is done, this representation - control from your DataTemplate will get DataContext property set to your View Model, here it will be local:MyViewModel instance.
That will give you a possibility to use binding in your DataTemplate, to bind in you DataTemplate directly to properties from you ViewModel.
Is that more clear now?

Showing UserControl once at the time with DataTemplateSelector

I have a couple specific user controls to Show some Content, e.g. simple like Image, WebControl but also two complex specific custom controls drawing on a canvas.
Now I thought using the DataTemplateSelector to handle the different UserControls. I actully used this http://tech.pro/tutorial/807/wpf-tutorial-how-to-use-a-datatemplateselector as a reference.
I changed the code so the form loads the UserControls dynamically (according to the file extension) in the following collection:
ObservableCollection<string> _pathCollection = new ObservableCollection<string>();
The only difference to the reference is now I want to navigate back and forward to the next control by showing one control only at the time. Which control should I use instead of ListView?
<Grid>
<ListView ScrollViewer.CanContentScroll="False"
ItemsSource="{Binding ElementName=This, Path=PathCollection}"
ItemTemplateSelector="{StaticResource imgStringTemplateSelector}">
</ListView>
</Grid>
How do I need to bind it to the template (equal to ItemTemplateSelector above)? WPF is still very new to me and I am learning.
Use a ContentControl. Bind your current item to the Content-property and the DataTemplateSelector to the ContentTemplateSelector-property.
<ContentControl Content="{Binding Path=CurrentItem, Mode=OneWay}", ContentTemplateSelector="{StaticResource imgStringTemplateSelector}" />
Your CurrentItem should be a DependencyProperty or a INotifyPropertyChanged-property of your DataContext. When you change your CurrentItem, the ContentControl will update the template automatically with help of your TemplateSelector.

Customizing Auto-Generated Columns in the DataGrid Control

After reading the excellent article on how to Customize Auto-Generated Columns, I have come across an issue.
While trying to customize auto-generated columns in a DataGrid control, I want to do simple things like make sure that all numeric column values are right-aligned. To this end, I created a DataTemplate as follows:
<DataGrid x:Name="MyGrid" AutoGeneratingColumn="MyGrid_AutoGeneratingColumn">
<DataGrid.Resources>
<DataTemplate x:Key="IntegerTemplate">
<TextBlock Text="{Binding}" HorizontalAlignment="Right"/>
</DataTemplate>
</DataGrid.Resources>
</DataGrid>
Then, in the AutoGeneratingColumn DataGrid event handler, I want to assign this generic DataTemplate as the CellTemplate for all integral (i.e., numeric) columns:
public void MyWindow_AdjustColumnTemplateBasedOnType(
DataGridAutoGeneratingColumnEventArgs e)
{
if (/*This is a column I want to change*/)
{
DataGridTemplateColumn column=new DataGridTemplateColumn();
column.Header=e.PropertyName;
column.CellTemplate=MyGrid.FindResource("IntegerTemplate") as DataTemplate;
e.Column=column;
}
}
The problem is that the value of the Text column of the TextBlock does not display the desired results. Instead of seeing the right justified value in each cell whose column has this DataTemplate as its CellTemplate, I see:
Using the empty binding syntax by setting the attribute Text to "{Binding}" is obviously incorrect. Setting a path based binding does produce the desired result. That is, if I set a (hard-coded data path) binding using something like:
<DataGrid.Resources>
<DataTemplate x:Key="IntegerTemplate">
<!-- Binding hard set to ProductId -->
<TextBlock Text="{Binding ProductId}" HorizontalAlignment="Right"/>
</DataTemplate>
</DataGrid.Resources>
Then all is well, but my generic DataTemplate is no longer generic. Instead of it being reusable for all integer columns, it can only be used for the ProductId column, because the binding is fixed to the value of that specific data item:
What is the correct binding I should use so that the generic DataTemplate actually uses whatever value is in the corresponding ItemSource property for the column with which it is associated.
I believe styles will solve your issue here .
private void MyGrid_AutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
{
if (/*This is a column I want to change*/)
{
DataGridColumn column = e.Column;
column.CellStyle = MyGrid.FindResource("IntegerTemplate") as Style;
}
}
and in XAML you can write
<Style TargetType="DataGridCell" x:Key="IntegerTemplate">
<Setter Property="FontWeight" Value="Bold"></Setter>
</Style>
When using a TemplateColumn in a DataGrid context you usually have to use a ValueConverter as well.
This ValueConverter converts the data from you ViewModels object (which is in fact the reason why you want to use a TemplateColum instead of an ordinary TextColumn) to an representable string form.
Do you have such a converter or do you provide a ToString() method in your ViewModels object ? If not try it, maybe that helps ...
For people struggling with the "What is the correct binding I should use so that the generic DataTemplate actually uses whatever value is in the corresponding ItemSource property for the column with which it is associated."
See Jay_Wang's answer in this link:
http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/8b2e94b7-3c44-4642-8acc-851de5285062
The important bit is overriding the System.Windows.FrameworkElement GenerateElement function so that it can programatically set up the binding to use the column name.
Hope this helps.

Can I databind to properties of a custom class (instantiated in xaml) that then forms the content of a templated listboxitem

Any help on this really appreciated. In summary I'm trying to databind to properties of a custom class instantiated in xaml that then forms the content of a templated listboxitem (phew!).
I have a simple c# class called MenuItem. It has two properties:
- Heading
- Icon
Concentrating on just one of those menu items (i.e. to provide a simple example of where I am stuck) If I do this (with the values hard coded) it works fine:
<ListBox>
<ListBoxItem ContentTemplate="{StaticResource MenuItemTemplate}">
<myclasses:MenuItem Heading="News" IconImage="News.png"/>
</ListBoxItem>
</Listbox>
Where MenuItemTemplate is an appropriate DataTemplate in the resources section binding each property) containing lines such as:
<TextBlock x:Name="tbHeading" Text="{Binding Heading}">
Wheareas when I try to use binding to set the Heading property it falls over (AG_E_PARSER_BAD_PROPERTY_VALUE error)- e.g.:
<ListBox>
<ListBoxItem ContentTemplate="{StaticResource MenuItemTemplate}">
<myclasses:MenuItem Heading="{Binding NewsHeading, Mode=OneWay}" Icon="News.png"/>
</ListBoxItem>
<Listbox>
I've wondered if it is because I'm doing some kind of double binding (i.e. the template is binding to a value on the MenuItem class that needs to be bound) and that's not possible? I've tried having the properties declared as dependency properties but no difference (although I only learned about those today so I may be missing something).
I know I could set the menuitem objects up in the view model, and bind from there, but I would like to understand why the above doesn't work (as for my purposes there are advantages in constructing the menu items in the xaml).
Thank you!!!!
Ian
thanks for sticking with this. I agree the listbox might not be needed - but even if I reduce it to just one item in a contentcontrol:
<ContentControl ContentTemplate="{StaticResource MenuItemTemplate}">
<myclasses:MenuItem Heading="{Binding NewsHeading, Mode=OneWay}" IconImage="News.png"/>
</ContentControl>
I still have the same problem - which is that I can get databinding to work within the content of a contentcontrol (prior to it being presented by the datatemplate referred to in ContentTemplate) using purely xaml.
I.e. the above bit of xaml doesn't work - it throws an error on the bit that binds the NewsHeading:
Heading="{Binding NewsHeading, Mode=OneWay}
So I am trying to understand whether what I'm doing is impossible, or whether it is but I'm doing it wrong.
Thanks.
Assuming that you have multiple MenuItem classes (because you're putting them in a listbox and ti wouldn't make sense to do that if you just had one). You need to bind the collection to the ItemsSource property of the ListBox.
Somehting like this:
<ListBox ItemsSource="{Binding MyMenuItems}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Heading}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Note that the above assumes you've set the DataContext on the page to an object with a property called MyMenuItems which is a collection of your MenuItem objects.
To see a full example of this, look at the default code created when you create a new "Windows Phone Databound Application".
Edit:
Based on your comments, it seems that a ListBox is not the most appropriate solution to your needs. A ListBox is designed/intended to take a collection of items and display them in a list.
If you have a number of different objects which you know about at design time and simply wish to have them one on top of another (giving the appearance of a list) you could simply put them inside a ScrollViewer and/or a StackPanel (or other appropriate container). Plus, you would still be able to databind if you did it this way.

Is it Possible to apply a DataTemplate to a Page?

I'm trying to follow the MVVM pattern laid out here: http://msdn.microsoft.com/en-us/magazine/dd419663.aspx#id0090097 I have this in my MainWindowResources.xaml file:
<DataTemplate DataType="{x:Type vm:VendorsViewModel}">
<vw:Vendors/> <--- I get a "Can't put a page in a style" error in blend with this
</DataTemplate>
and I've got this in my MainWindow.xaml file
<Window.Resources>
<ResourceDictionary Source="MainWindowResources.xaml"/>
</Window.Resources>
The mainWindow.xaml file contains a menu on the left and page holder on the right. Can I apply a dataTemplate to a <Page>? Or does it have to be a <UserControl>? As it stands, nothing is being data bound, here's what I have on the page that I want to have the viewmodel applied to:
<Custom:DataGrid Margin="0,30,0,0" d:LayoutOverrides="Width" ItemsSource="{Binding Path=AllVendors, Mode=Default}" >
<Custom:DataGrid.Columns>
<Custom:DataGridTextColumn Header="Company Name" Binding="{Binding Path=Name}" />
</Custom:DataGrid.Columns>
</Custom:DataGrid>
DataTemplates are applied to Content, which in most cases is either the Content property of a ContentControl or the Items/ItemsSource property of an ItemsControl. Page is not derived from ContentControl (UserControl is) so a DataTemplate can't be applied to its Content.
From what you're doing here it doesn't sound like that's what you're trying to do though. It looks like you're trying to use a Page in a DataTemplate which is what the error is telling you. Page is treated like Window in that it is a root container that is intended to have visual Content defined in a xaml file. UserControl has a similar purpose but can be inserted anywhere into a layout. If you change vw:Vendors to be a UserControl that should get rid of this specific error but you should also consider whether you're gaining anything from having the UserControl instead of just putting its content directly into the DataTemplate - this can help discourage code-behind and force you to use your ViewModel correctly.

Resources