Binding Grid Height Using FindAncestor and AncestorLevel To Another Grid - wpf

My question is about properly binding the property of one element to another.
Here is the structure of my code:
Data Template contains a grid (let's called the grid GridA), and in the grid, I specify an instance of a control, called ControlA.
ControlA is a UserControl that contains a Grid. There is custom logic in ControlA (code-behind) that dynamically builds the content - but in a nutshell, it uses another data template.
The data template for ControlA consists of another Grid. I want to bind the Height property for this grid in this data template to the Height property of the Grid in the data template referenced in my first bullet point above (the grid called GridA).
Here is the XAML that I have used for the binding, but, essentially, it isnt working, as its not finding the Grid:
<Grid Height="{Binding Path=ActualHeight,
RelativeSource={RelativeSource FindAncestor,
AncestorType=Grid,
AncestorLevel=2},
UpdateSourceTrigger=PropertyChanged}">
</Grid>
I understand that by specifying AncestorLevel=2, that it will use the second "found" occurrence of the type that you are looking for, in this case, the type is Grid. So in my mind, it will first find the Grid in ControlA, then it will continue to walk up the tree and find the Grid in the first data template, which is the Grid named GridA. This should be the second occurrence, correct?

Since you're beginning your search from the second grid, you actually want ancestor level = 1 (which is the default). Observe:
<Grid x:Name="first">
<Grid x:Name="second">
<Grid x:Name="third" Tag="{Binding Name, RelativeSource={RelativeSource FindAncestor, AncestorType=Grid, AncestorLevel=2}}">
<!-- displays "first", not "second" -->
<TextBlock Text="{Binding Tag, ElementName=third}"/>
</Grid>
</Grid>
</Grid>
BTW, your design sounds like it fights WPF's layout system rather than embracing it. As such, you're probably creating a lot of unnecessary pain for yourself.

Am I missing something, or could you just use the SharedSizeGroup property of the RowDefinitions and set Grid.IsSharedSizeGroup="True" on the outermost control?

Related

What's a better way to bind header checkbox to user control's datacontext?

I have a workable solution but I'm pretty convinced there's a better way of writing this.
I have a User Control with a Data Grid inside. The Data Grid's ItemsSource is set to {Binding Path=MyView} where MyView is an ICollectionView property of the View Model. The User Control's data context is set to the View Model.
In the data grid, I have a check box header. I want to bind the IsChecked state of the checkbox to a property in the View Model.
This is what I have so far and it seems to work, but I'm concerned this binding is unnecessarily complex. The UI is pretty basic so I would expect the binding to be more straightforward to write than it was.
Is there a better way to express such a binding?
<DataGridTemplateColumn.HeaderTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Views:MyUserControlClass}}, Path=DataContext.AllRowsSelected}" />
</DataTemplate>
</DataGridTemplateColumn.HeaderTemplate>
In such situations I use
ElementName=userControl
instead of
RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Views:MyUserControlClass}}
Also you can use
{Binding Parent.DataContext.AllRowsSelected, ElementName=LayoutRoot}
In this case I assume that LayoutRoot is the name of the element who's parent is the user control. Parent is its property. So binding is set to parent's DataContext property.
I prefer the last variant, because providing name for user control limits its usage.
EDIT
About LayoutRoot. This name is often provided for the top element in a Window or a UserControl, or just some layout:
<Window ...>
<Grid Name="LayoutRoot">
...
</Grid>
</Window>
There's nothing special about this name. Just often used. Same situation as with namespace aliases in xaml: sys (points to mscorlib), local (points to your application namespace), etc.

What standard WPF control should I use?

I am trying the master-detail presentation in my app: when an item in a listbox is selected, its details are displayed in an adjacent control.
This control will have a list of measurements such as height, width, weight, etc. It will also have some small graphics such as a green or red dot or a medium sized image. It will also have some rich text.
Which STANDARD WPF control should I use to contain all these elements. I am thinking of using a listbox but wonder if there are better controls to use.
My main consideration is ease of coding, then possibly efficiency of the code.
Thanks.
A listbox indicates a list of items that can be tailored using a DataTemplate for appearence. In this case you are showing the details of a selected item. I would actually use a container such as a Grid nested in your current UI and have a set of stackpanel including the details of the selected item.
<Grid>
<StackPanel>
<StackPanel Orientation="Vertical">
<TextBlock>Detail1</TextBlock>
<TextBox></TextBox>
</StackPanel>
<StackPanel Orientation="Vertical">
<TextBlock>Detail2</TextBlock>
<TextBox></TextBox>
</StackPanel>
</StackPanel>
</Grid>
This is only one suggestion but the point is to use a container and use a set of controls in the containers - textblock,textbox,checkboxes(boolean details), etc... this will allow you to use any control type necessary to represent the specific data field of the selected item.
You don't want to use a listbox unless you have a collection of similar items, and you want one or more items to be 'selected' at some point. It sounds like that is not what you want for the details part.
You do have a collection, which is shown in your master list. You should bind the SelectedItem in your master list to a property in your viewmodel. Then you can bind that same property to the details section of your UI. When the selection in the master list changes, your details UI will automatically update to reflect the changes.
<ListBox x:Name="masterList" ItemsSource="{Binding MyItems}" SelectedItem="{Binding MySelectedItem, Mode=TwoWay}"></ListBox>
<UserControl x:Name="detailsControl" DataContext="{Binding MySelectedItem}"> </UserControl >

Variable Binding-Path

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.

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.

Display images in TextBlock (WPF)

I'm working on a simple chat application. Currently the messages are binded to a custom-styled listbox like this (simplified XAML):
<ListBox ItemsSource="{Binding MessageCollection}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Text}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Now I would like to be able to put images (like graphical smileys) into the displayed message text. Is there any way to achieve this using TextBlock (or any other standart component) or do I need to use some special control for this?
Thanks in advance
Just use the InlineUIContainer.
<TextBlock TextWrapping="Wrap">
<Run>Some text.</Run>
<InlineUIContainer>
<Image Source="http://sstatic.net/stackoverflow/img/apple-touch-icon.png" Height="20"></Image>
</InlineUIContainer>
<Run>Some more text.</Run>
</TextBlock>
The Content of a TextBlock is always just a series of Inlines, so you should use the InlineUIContainer. You can insert this container as one of the Inlines in your TextBlock wherever you want an Image to appear, alternating with text Runs. You could parse a message and at the same time keep adding the tokens (either text or images) that you find to the Inlines collection of the TextBlock.
If you want the Images actually inside the text (like an emoticon), then you are going to have to do some work. This sounds like one of the few times I would actually want a User Control, the point of which would be one that scans the Text looking for emoticon values and building a Data Template on the fly.
Remember that anything you can do in XAML you can do in code, so the code I'm thinking of would follow this general idea:
Scan text for emoticon values and
create a list of values for data
elements.
Create a DockPanel.
Foreach element in the List, add
either a TextBlock or an Image
(based on value).
Set this.Content to the DockPanel.
I think something like this is actually what you are looking for, but if you want just an Image, then the ValueConverter suggestion would work.
You could use a value converter to convert the text to another type which has a list of segments which are composed of either text or the smiley face (in the order in which they appear).
Then, you can use a data template to bind to that new type and display the text and smiley faces appropriately.
I also encountered this problem recently and I overcome this by
Creating an ListBox ItemTemplate containing an ItemsControl that has a WrapPanel in the ItemsPanelTemplate and then binding my string to the ItemsSource of the ItemsControl with a IValueConverter that houses all the logic.
Split out your words and query/search your emoticons strings, hyperlinks etc and create your TextBlock, Image, Hyperlink, Button elements and set your values and event handles.
In the function create a List<UIElement> and populate the List with the controls you have generated and return the List as the object in the Convert function of the IValueConverter.
Because you have the WrapPanel in there you get your wrapping done.
Use the Image element instead of the TextBlock and use a Converter to map the text value to the smile image.
<ListBox ItemsSource="{Binding MessageCollection}">
<ListBox.ItemTemplate>
<DataTemplate>
<Image Source="{Binding Text, Converter={StaticResource MyImageConverter}}"/>
</DataTemplate>
</ListBox.ItemTemplate>

Resources