WPF Data Binding - Data Template is not Getting Values - wpf

I have the following DataTemplate:
<DataTemplate DataType="{x:Type Client:WorkItem}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="200" />
</Grid.ColumnDefinitions>
<Label Name="lblIDText" Margin="2">WI ID:</Label>
<Label Name="lblID" Margin="2" Grid.Column="1"
Target="{Binding Id}"></Label>
<Label Name="lblTitleText" Grid.Row="1" Margin="2">WI Title:</Label>
<Label Name="lblTitle" Margin="2" Grid.Row="2" Grid.ColumnSpan="2"
Target="{Binding Title}"></Label>
</Grid>
</DataTemplate>
in my <Window.Resources> section. It is meant to show the Id and Title of a WorkItem object (from namespace Microsoft.TeamFoundation.WorkItemTracking.Client.)
I try to put this in a TabItem in a TabControl and it only shows the static text. (The WorkItem ID and title do not show, but the static text in my template does.)
Clearly the template is being fired, but the binding is not working and I can't seem to see why.
Here is my C# that calls it:
private void PickWorkItem_Click(object sender, RoutedEventArgs e)
{
int Id = int.Parse(((Button) e.OriginalSource).Tag.ToString());
_mediator.PickedWorkItem = GetWorkItemInQueryResultListByID(Id);
tabAddLinks.DataContext = _mediator.PickedWorkItem;
tabAddLinks.Content = _mediator.PickedWorkItem;
}
I tried it with DataContext in and out and it works the same. When I debug, the value for _mediator.PickedWorkItem is set correctly (Id and Title are both fine).
Any ideas on how to fix this would be appreciated.

You're binding the Label's Target property, when you actually want to be binding it's Content property:
<Label Content="{Binding Id}"/>
Also, consider using a TextBlock instead of a Label if you don't need the extra functionality a Label provides:
<TextBlock Text="{Binding Id}"/>

You are binding Label.Target. Target is the UIElement being labelled. You need to bind Label.Content, or change it to a TextBlock and bind TextBlock.Text.
(My guess would be that you were trying to bind a non-existent Label.Text property and Intellisense helpfully chose Target for you instead...!)

I'm pretty new to WPF, so excuse me if I'm off base, but don't you need to set the Content property of the label, not the Target?

Related

WPF ContentControl does not collaspe on Visibility change

I have a tab sequence that I want to be able to split into two regions or recombine into a single tab sequence. The application will start off with one tab sequence
Allow the user to split into a second panel
And recombine into a single tab control.
At this stage, I should get the same display at the first image. But, instead, the ContentControl does not Collaspe and space is left in the main Grid. There is also a GridSplitter that devides the two areaa (white bar in image 2). That seems to Collapse as expected. Is there some trick to get it to get the ContentControl to Collapse?
Here's the XAML. There's a boolean property BottomTabDisplayed which indicates if there are any controls in the bottom area:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ContentControl x:Name="ControlContainerTopTab" Grid.Row="0">
<TabControl SelectedIndex="{Binding SelectedIndex}"
>
<TabControl.ItemContainerStyle>
...
</TabControl.ItemContainerStyle>
</TabControl>
</ContentControl>
<GridSplitter x:Name="GridSplitterTabControls"
Grid.Row="1"
Height="5"
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
Visibility="{Binding BottomTabDisplayed,
Converter={StaticResource VisiblityToBoolConverter}}" />
<ContentControl Grid.Row="2" Visibility="{Binding BottomTabDisplayed, Converter={StaticResource VisiblityToBoolConverter}}">
<TabControl SelectedIndex="{Binding SelectedBottomIndex}"
Visibility="{Binding BottomTabDisplayed,
Converter={StaticResource VisiblityToBoolConverter}}"
>
<TabControl.ItemContainerStyle>
...
</TabControl.ItemContainerStyle>
</TabControl>
</ContentControl>
</Grid>
Edit 1:
And, here's the converter
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
bool isVisible = (bool)value;
return (isVisible ? Visibility.Visible : Visibility.Collapsed);
}
I just noticed something interesting, if the GridSplitter is unchanged, it works as expected. If I move the GridSplitter, then I get the empty space.
Edit 2:
Snoop changes when moving the GridSplitter
If you look at your code for VisiblityToBoolConverter, you will very likely see it is returning Hidden instead of Collapsed

ListBox with customized Item in VB.NET

I want to modify the original ListBox control that the each item to have a CheckBox, Labels and a Button control inside.
Is there any optimal method to make that? without making Custom Control from the very beginning?
Making custom control that inherits ListBox could be not a bad idea, but don't know how...
Thank you!
I tried WPF but it was too difficult at this time. Actually, designing the control via XAML was easy, but managing the list items(add/delete with texts, get event from the button in each item) wasn't.
Since the question is tagged [WPF] I'm going to provide a WPF answer:
The first thing any developer who faces WPF immediately tries to do is to use it as if it were winforms. This is a big mistake.
If you're working with WPF, you really need to leave behind the traditional aproach used in archaic technologies such as winforms, and understand and embrace The WPF Mentality.
in WPF, you don't "add/delete with texts, get event from the button in each item" or any of that, simply because UI is not Data.
Instead, you define a simple Data Model:
public class MyData
{
public string MyText1 {get;set;}
public string MyText2 {get;set;}
}
and then declaratively define Data Bindings in the UI to "show" this data to the UI as opposed to "reading" or "writing" data to/from the UI:
<Window x:Class="WpfApplication14.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication14"
Title="MainWindow" Height="350" Width="525">
<ListBox ItemsSource="{Binding}" HorizontalContentAlignment="Stretch">
<ListBox.ItemTemplate>
<DataTemplate>
<Border Margin="5" Background="LightCyan" BorderBrush="LightSkyBlue" BorderThickness="2">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Label Grid.Row="0" Grid.Column="0" Content="Text 1:" HorizontalAlignment="Right"/>
<Label Grid.Row="1" Grid.Column="0" Content="Text 2:" HorizontalAlignment="Right"/>
<TextBox Grid.Row="0" Grid.Column="1" Text="{Binding MyText1}"/>
<TextBox Grid.Row="1" Grid.Column="1" Text="{Binding MyText2}"/>
<Button Grid.Row="2" Grid.ColumnSpan="2" Content="Button" HorizontalAlignment="Center"/>
</Grid>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Window>
And finally, you define the DataContext of the Window or View to a relevant instance or collection of such data:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = Enumerable.Range(0,10)
.Select(x => new MyData()
{
MyText1 = "Text1 - " + x.ToString(),
MyText2 = "Text2 - " + x.ToString()
});
}
}
All this results in:
See? really simple and beautiful.
Forget winforms, WPF Rocks. Just copy and paste my code in a File -> New Project -> WPF Application and see the results for yourself.
The best way to do this, short of using WPF, is to create a custom UserControl which represents each item that will go in the list. Then, add a FlowLayoutPanel to your form. Set the panel's AutoScroll property to True. Then set its FlowDirection property to TopToBottom. Then, dynamically create one of your custom controls for each item that you need to add to the list and call the panel's Controls.Add method to add them to the list.

Set ContentControls Content Property from codebehind throws exception 'value does not fall in range'

I got following xaml binding scenario working in WPF. Define UIElements in Grid's ressources. Bind those static resources to a ToggleButton's Tag property. On toggle button click assign the Tag property to the Content property of ContentControl.
<Grid>
<Grid.Resources>
<TextBlock x:Key="t1"
Grid.Row="1"
Text="Text1" />
<TextBlock x:Key="t2"
Grid.Row="1"
Text="Text2" />
</Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal"
HorizontalAlignment="Center">
<ToggleButton Tag="{StaticResource t1}"
Margin="10"
Click="ButtonBase_OnClick"
Content="T1" />
<ToggleButton Tag="{StaticResource t2}"
Margin="10"
Click="ButtonBase_OnClick"
Content="T1" />
</StackPanel>
<ContentControl x:Name="cc"
Grid.Row="1" />
</Grid>
The toggle button click just assigns the Tag value to the Content property.
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
cc.Content = ((FrameworkElement)sender).Tag;
}
While this works in almighty WPF it is not possible in WinRT. WinRT complains with an ArgumentException 'Value does not fall within the expected range.'. I have no clue why?
For testing purposes I tried a direct assignment in the event handler, Which worked as expected:
cc.Content = new TextBlock { Text = "Text1" };
To make it even more bizarre I tried this one in WinRT:
<ContentControl x:Name="cc"
Content="{StaticResource t1}"
Grid.Row="1" />
Result: It works in the designer, but fails at runtime. Even more clueless about that.
First of all what does the ArgumentException is trying to tell me? Second why is it working in WPF? What about the discrepancy between runtime and VS designer?
In Win RT/Windows 8 Store Apps, the Content Control cannot contain an element that is already present in the view somewhere else. As it exists in Grid.Resources, it cannot be added to ContentControl.
I suggest making a DataTemplate instead in the resources:
<Grid>
<Grid.Resources>
<DataTemplate x:Key="t1">
<TextBlock Text="Text1" />
</DataTemplate>
...
And in the button click something like:
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
var dataTemplate = ((FrameworkElement)sender).Tag as DataTemplate;
cc.Content = dataTemplate.LoadContent() as FrameworkElement;
}
Note: I don't have the chance to test this.

ListBox inside ListBox, how do I know which button is clicked?

I have two ListBoxes, one inside another. And both ListBoxes will have items dynamically added into them upon user request.
Each time a button (not shown in the below code snippet) is clicked, a new item is added to the listbox. Each new item includes a new listbox and others.
And I have buttons inside the inner listbox, one for each list item of the inner listbox.
With DataContext, I can find the data binded to the inner list item, and make changes to it, so that changes are reflected on the proper list item.
However, I also need to make changes to the data binded to the outer list item, which corresponds to the button. How can I know which one it is?
I have came up with a solution, which I believe it not elegant enough. By making changes to the model, I can have each inner data holds a reference to the outer data, so that I can find the data binded to the outer list item. This doesn't seem like a proper solution though. Do you have any suggestions?
Below is code snippet of the xaml. I've simplified it, hope it's easy to understand. I feel you don't have to read the whole code.
<ListBox Name="QuestionsListBox" HorizontalAlignment="Stretch" ItemContainerStyle="{StaticResource ListItem}" >
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBox Text="{Binding Question, Mode=TwoWay}" Grid.Row="0" HorizontalAlignment="Stretch" TextWrapping="Wrap"/>
<ListBox Name="ChoicesListBox" ItemsSource="{Binding Choices}" Grid.Row="1" HorizontalAlignment="Stretch" ItemContainerStyle="{StaticResource ListItem}">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid HorizontalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Button Grid.Column="0" Click="ChoiceAddButton_Click" Height="72" Width="72" HorizontalAlignment="Left" BorderBrush="Transparent">
<Button.Background>
<ImageBrush ImageSource="/Images/choices.add.png" Stretch="Fill" />
</Button.Background>
</Button>
<TextBox Text="{Binding Value, Mode=TwoWay}" HorizontalAlignment="Stretch" Grid.Column="1" Margin="-20,0" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Why not just use QuestionsListBox.DataContext inside ChoiceAddButton_Click directly? You have a direct way to reference the outer ListBox from your code behind since you've given it a name, and DataContext is an accessible property.
private void ChoiceAddButton_Click(object sender, RoutedEventArgs e)
{
...
var outerLBDataContext= QuestionsListBox.DataContext;
...
}
This works fine for me in a demo solution using your provided XAML.
Edit 2:
Sorry, wasn't thinking. The Button's DataContext will be a Choice, not the Choices collection.
Your inner ListBox's DataContext is not a Question, it's Choices. Your outer TextBox has Question.Question as its DataContext. Binding Text or ItemsSource makes the DataContext point to the binding target. Here is a bit of tricky XAML to sneak in a DataContext reference.
Add an ElementName to your outer TextBox:
<TextBox Text="{Binding Question, Mode=TwoWay}" Grid.Row="0" HorizontalAlignment="Stretch" TextWrapping="Wrap" ElementName="questionTextBox"/>
Now, add a hidden TextBlock inside your inner ListBox:
<ListBox Name="ChoicesListBox" ItemsSource="{Binding Choices}" Grid.Row="1" HorizontalAlignment="Stretch" ItemContainerStyle="{StaticResource ListItem}">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid HorizontalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Button Grid.Column="0" Click="ChoiceAddButton_Click" Height="72" Width="72" HorizontalAlignment="Left" BorderBrush="Transparent">
<Button.Background>
<ImageBrush ImageSource="/Images/choices.add.png" Stretch="Fill" />
</Button.Background>
</Button>
<TextBox Text="{Binding Value, Mode=TwoWay}" HorizontalAlignment="Stretch" Grid.Column="1" Margin="-20,0" />
<TextBlock Name="hiddenTextBlock" Visibility="Collapsed" DataContext="{Binding ElementName=questionTextBox, Path=DataContext}"
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Finally, inside your event handler, you can navigate around the tree to get that reference:
private void ChoiceAddButton_Click(object sender, RoutedEventArgs e)
{
Button btn = sender as Button;
if(btn == null) return; //won't happen when this method handles the event
Grid g = btn.Parent as Grid;
if(g!=null) // also unlikely to fail
{
TextBlock tb = g.FindName("hiddenTextBlock") as TextBlock;
if(tb!=null) // also unlikely to fail, but never hurts to be cautious
{
var currentQuestion = tb.DataContext;
// now you've got the DC you want
}
}
}
I'd like to note that this isn't really an elegant solution. It is an all UI solution, however, which could be a useful thing. But better design would be to include Parent references in your Choice and ChoiceList (or whatever they're called) classes and use that. Then it would be as simple as calling btn.DataContext.Parent.Parent with appropriate type conversions. This way your code becomes easier to read and maintain.
You could add an event to your inner model that your containing datamodel subscribes to before adding it to the 'Choices' collection and pass the relevant information that way.
Is this necessary to use the Button control in your solution ??
If not fixed, then you can use the "Image control" as specified below <Image Source="/Images/choices.add.png" Height="72" Width="72" HorizontalAlignment="Left" Stretch="Fill"/>
If you use the Image control then in combination with this you can add the selection changed event to inner list box ( ChoicesListBox). Then in the Handler you can get the item selected as it comes as parameter with the selection changed event(SelectionChangedEventArgs).
Modify the List box and add the Selection changed event handler as below
<ListBox Name="ChoicesListBox" ItemsSource="{Binding Choices}" Grid.Row="1" HorizontalAlignment="Stretch" ItemContainerStyle="{StaticResource ListItem}" SelectionChanged="Items_SelectionChanged">
in page.xaml.cs you can add the handler and access the item as follows
private void Items_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (e.AddedItems[0] != null)
{
//Please use the casting to the Choices items type to make use.
var temp = (ChoicesItemViewModel)e.AddedItems[0];
}
}

WPF DataBinding: Grid ColumnDefinition Width to UserControl DependencyProperty

I'm not sure what I'm doing wrong, but I am attempting to align my user control with the ColumnWidth of my grid using DataBinding and DependencyProperties.
I have a GridView on my Form Page which contains a two instances of a user control (Address). The address usercontrol contains a textlabel "header" so that I can name the user control "Office Address" or "Mailing Address" etc and is formatted using it's own GridView. I added a DependencyProperty to the user control called "HeaderWidth" which is simply an Int32. The problem is that even with the bindings, things don't align. I'm not sure what I'm doing wrong.
AddressField.xaml:
<UserControl x:Class="AddressField" x:Name="PART_AddressFieldControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
>
<StackPanel Orientation="Horizontal">
<Grid x:Name="StandardView">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{Binding ElementName=PART_AddressFieldControl, Path=HeaderWidth}" />
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="Auto" />
<!-- ETC REMOVED FOR COMPACTNESS -->
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" HorizontalAlignment="Right" Text="{Binding ElementName=PART_AddressFieldControl, Path=Header}" />
<TextBox Grid.Row="0" Grid.Column="1" Grid.ColumnSpan="8" MinWidth="300" Text="{Binding Address1}" />
<!-- ETC REMOVED FOR COMPACTNESS -->
</Grid>
</StackPanel>
</UserControl>
AddressField.cs
public partial class AddressField : UserControl
{
static AddressField()
{
HeaderProperty = DependencyProperty.Register("Header", typeof(String), typeof(AddressField), new UIPropertyMetadata("Address:"));
HeaderWidthProperty = DependencyProperty.Register("HeaderWidth", typeof(Int32), typeof(AddressField), new UIPropertyMetadata());
ViewProperty = DependencyProperty.Register("View", typeof(AddressFieldView), typeof(AddressField), new UIPropertyMetadata(AddressFieldView.STANDARD));
}
public AddressField()
{
InitializeComponent();
}
public String Header
{
get { return (String)GetValue(HeaderProperty); }
set { SetValue(HeaderProperty, value); }
}
public static readonly DependencyProperty HeaderProperty;
public Int32 HeaderWidth
{
get { return (Int32)GetValue(HeaderWidthProperty); }
set { SetValue(HeaderWidthProperty, value); }
}
public static readonly DependencyProperty HeaderWidthProperty;
}
RegistrationForm.xaml
<!-- ETC REMOVED FOR COMPACTNESS -->
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" Name="FirstWidth" />
<ColumnDefinition Width="Auto" />
<!-- ... ETC ... -->
</Grid.ColumnDefinitions>
<vws:AddressField Grid.Row="1" Grid.Column="0" Grid.RowSpan="3" Grid.ColumnSpan="9" Header="Office Address:" DataContext="{Binding OfficeAddressVM}" HeaderWidth="{Binding ElementName=FirstWidth, Path=ActualWidth}" />
<vws:AddressField Grid.Row="4" Grid.Column="0" Grid.RowSpan="3" Grid.ColumnSpan="9" Header="Mailing Address:" DataContext="{Binding MailingAddressVM}" HeaderWidth="{Binding ElementName=FirstWidth, Path=ActualWidth}" />
Basically, I'm trying to align the textlabel of the usercontrol with the registrationform first column width.
EDIT:
Oh, and just because I was curious: I put a debugging breakpoint and dug deep into the UI Tree and got to the AddressField's "Header" (TextBlock) and the ActualWidth is not 0.0 but 73.9 or something like that, yet the UI doesn't take this into account, it still is not visible because the width is 0.0.
There are easier ways you know :)
Simply put Grid.IsSharedSizeScope="True" on the grid in your main window (not in the control), and SharedSizeGroup="someRandomName" on all the columns you wish to share width - in this case, only the column you've bound in the user control, i.e. replace the binding with SharedSizeGroup. That's it! Any columns with the same SharedSizeGroup value will now be the same size, which is the size of the largest of them.
Granted, it only works well with Auto sizing but it sounds like that's what you want.
For more information you can look here and here.
Edit: as to what you were doing wrong, I suspect that the outer grid column, being sized to Auto, but not having any contents of its own to size by (because all the controls were spanning multiple columns), took on a width of zero. The bindings then propagated to the user controls. There are a lot of complicated interactions there though, and I don't have the entire XAML to judge, so I might be wrong.

Resources