Get object value from Listbox Item - c#

Im currently developing an Windows Phone 7.0 Application with C#. I got an Listbox whichs items is based on the results of an WCF-Service. I've created my own object which contains a few values that have been fetched from an database. The problem is that i don't know how to change the content of each Listbox Item depending on the values of that items parameters that was fetched using the webservice.
Structure:
User enters the XAML Page
An connection is being opened between the client and the webservice
The webservice returns an List<Friend> The important parameter inside Friend is called Verified
The listbox items is set using: lstFriends.ItemsSource = e.Result;
What i want to do:
I want to check in each Listbox Item after the parameter Verified and check it's value.
Depending on if the parameter is true or false a TextBlock inside the Listbox Item should have different text.
Thanks

This is probably an ugly way to do it with no code-behind, but what you could do is create a template for your listboxitem that includes two textblocks (something) like this:
<Style x:Key="ListBoxItemStyle1" TargetType="ListBoxItem">
<Setter Properties... />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<TextBlock Text="Verified" Visibility={Binding Verified, Converter="{StaticResource BoolToVisibilityConverter}" />
<TextBlock Text="Not Verified" Visibility={Binding Verified, Converter="{StaticResource ReverseBoolToVisibilityConverter}" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
You would then have to write two converters: BoolToVisibilityConverter to change the boolean value of Verified to Visible if True and Collapsed if False for the first text block and ReverseBoolToVisibilityConverter to change the boolean value of Verified to Visible if False and Collapsed if True on the second text block. This way one textblock will always be visible in the listboxitem and one will always be collapsed, depending on the value of the Verified property.
If you don't know how to do value converters, you can look HERE.
This isn't tested and is not all of the code you'd need, but it should work. This assumes that the two different text blocks will always contain the same text and that the Verified property is a boolean property, if not then you might want to figure out another way to do it.
On second thought, you could just do ONE value converter and one textblock and convert the value of Verified to the text you want. That would be easier.
<TextBlock Text="{Binding Verified, Converter="{StaticResource VerifiedToTextConverter}" />

Related

How to re-draw a single item inside a ListView which uses DataTriggers to select templates for ListViewItems?

In my code, I only have one ListView element that goes over a bunch of conditions and MultiDataTriggers to determine the correct template for each ListViewItem that is supposed to be drawn on the display.
This ListView element defines a ListView.ItemContainerStyle which then defines a ContentTemplate property for each item and in this part, I have declared the conditions for selecting each template. [Code Shown Below]
From this design, my understanding is that the responsibility of creating the elements and drawing the appropriate template is on ListView's shoulders, not the ListViewItems. Therefore, when I change something about the DataContext of ListViewItems and call "UpdateLayout()" on them, nothing happens because they don't know how else to re-draw themselves.
The changes that I make to ListViewItems' DataContexts, are made to the same properties that are important when choosing the right template in the beginning. So, the template itself needs to be changed, not the insides of the elements, and this is my problem.
There is an Error class and it has a property named List<string> Suggestions that may have some items inside of it or nothing. When choosing the template, Suggestions.Count is decisive.
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding Suggestions.Count}"
Value="0" />
</MultiDataTrigger.Conditions>
<Setter Property="ContentTemplate"
Value="{StaticResource SingleSuggestionErrorTemplate}" />
</MultiDataTrigger>
In run-time, the users see a ListViewItem that has no "More Suggestions" box, but by using a button, they can add a new one for the item. Then, the ListViewItem has to be re-drawn and show the template that displays another set of elements which includes the "More Suggestions" box. Here's an example of code-behind for doing this and also the way I access the parent Items:
async private void SubmitUserSuggestionButton_Click (object sender, RoutedEventArgs e)
{
// Get the Expander element.
Visual exp = ((DependencyObject)e.Source).GetParent<Expander>();
// Access the parent ListViewItem of the Expander
ListViewItem LVItem = exp.GetParent<ListViewItem>();
// Get the location of the Error inside document.
Error LVItem_Error = (Error)LVItem.DataContext;
if (LVItem_Error.Word == "something")
{
LVItem_Error.Suggestions.Add("Custom Suggestion!");
// At this point, the previous template which showed no suggestions
// is no longer valid and the template has to be chosen again,
// based on the new count of the list.
LVItem.UpdateLayout();
return;
}
}
I have tried using ObservableCollection instead of List and failed. Also, I have implemented INotifyPropertyChanged on the collection and that has failed too. The problem with the second approach may be that Collection.Add() does NOT fire PropertyChanged, but I'm not entirely sure that if I handled that problem, it would be useful since the ListViewItem itself is not capable of deciding what template to choose and the upper-level ListView has to do that.
I should mention that I'm aware of ListView.Items.Refresh() and it works for my scenario, but it's not helpful because I don't want and need to re-draw every item inside the list.
At last, What I'm hoping to do is to tell the upper-level ListView to "Refresh" only one specific item of its collection, not the whole bunch. I'd be very grateful for any and all answers or pointers.
The ListView:
<ListView Name="lvErrors">
<!-- Main ListView Item Style -->
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<ContentControl Content="{Binding}">
<ContentControl.Style>
<Style TargetType="ContentControl">
<Style.Triggers>
<!-- Definition of MultiDataTriggers and Conditions -->
.
.
.
Thanks in advance.

WPF ContentPresenter Content null when Visibility=Collapsed

I'm making custom control with edit/view state.
I've made 2 dependencyProperties with default styles:
<Setter Property="EditContent">
<Setter.Value>
<TextBox Text="{Binding ElementName=parent, Path=LocalValue}" />
</Setter.Value>
</Setter>
<Setter Property="ViewContent">
<Setter.Value>
<TextBox IsEnabled="False" Text="{Binding ElementName=parent, Path=LocalValue}" />
</Setter.Value>
</Setter>
and then, displaying these Contents depending on IsReadOnly value like this:
<Border Background="Transparent"
MouseLeftButtonDown="UIElement_OnMouseLeftButtonDown"
Visibility="{Binding ElementName=parent,
Path=IsReadOnly,
Converter={StaticResource BooleanToCollapsingVisibilityConverter},
ConverterParameter=true}">
<ContentPresenter Content="{Binding ElementName=parent, Path=ViewContent}" />
</Border>
Problem is, that when my control loads with IsReadOnly = true, Content Property of my ContentPresenter for EditContent is null.
When I'm changing IsReadOnly to false Content of EditContent loads, but my binding does not work (like it's not evaluated).
How to re-evaluate bindings in WPF, or force ContentPresenter to load it's content on created (even if it's invisible)?
P.S. If I navigate to this ContentPresenter in Snoop (or WPF Inspector) when It's invisible - it's empty. When I navigate to it when it's visible - bindings starting to work
Please, have a look at output windows while debugging. you will see errormessage describing the binding problem. wpf rule nr.1: always check output window.
The reason is that your edit / view content has different NameScope, therefore ElementName does not work. However, in your Control you can set NameScope manually, by using something like:
var currentScope = NameScope.GetNameScope(this);
NameScope.SetNameScope((UIElement)this.EditContent, currentScope)
in your case you are using styles and styles has its own namescope, so it won't work. Imagine, that you used the style on multiple pages. What element should be used?
Sometimes you can use Source={x:Reference elementName}, but you cannot use it in direct children of the source the element, because the element does not exist yet, when the {x:Reference } is being resolved
never set content-like properties inside styles. if you applied your style to more than one element, that the same TextBox from ViewContent would be added to visual tree multiple times and that throws an exception. You should use DataTemplates instead of direct content in styles

Passing Non-Item Values to Properties in ItemTemplate

Today I'm having trouble passing values from a parent control down to the properties of a child control in a list.
I have a custom control which I've made which functions as a Thumbnail Check Box. Essentially it's just a checkbox wrapped around an image with some nice borders. It's all wrapped up into a DLL and deployed as a custom control
If I want to use a single instance of the control, I can do so like this...
<tcb:ThumbnailCheckBox IsChecked="True"
ImagePath="D:\Pictures\123.jpg"
CornerRadius="10"
Height="{Binding ThumbnailSize}"
Margin="10" />
Code Listing 1 - Single Use
This works great, and easily binds to ThumbnailSize on my ViewModel so I can change the size of the image in the control however I want.
The problem is when I want to expand the use of this control into a list, I'm running into a few problems.
To begin, I've styled the ListBox control to meet my needs like so...
<Style TargetType="{x:Type ListBox}"
x:Key="WrappingImageListBox">
<!-- Set the ItemTemplate of the ListBox to a DataTemplate
which explains how to display an object of type BitmapImage. -->
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<tcb:ThumbnailCheckBox ImagePath="{Binding ImagePath}"
IsChecked="{Binding Selected}"
Height="{TemplateBinding utilities:MyAttachedProperties.ImageSize}"
CornerRadius="8"
Margin="10">
</tcb:ThumbnailCheckBox>
</DataTemplate>
</Setter.Value>
</Setter>
<!-- Swap out the default items panel with a WrapPanel so that
the images will be arranged with a different layout. -->
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
<!-- Set this attached property to 'Disabled' so that the
ScrollViewer in the ListBox will never show a horizontal
scrollbar, and the WrapPanel it contains will be constrained
to the width of the ScrollViewer's viewable surface. -->
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility"
Value="Disabled" />
</Style>
Code Listing 2 - ListBox Style
And I call it like this from my main view...
<ListBox ItemsSource="{Binding DirectoryPictures}"
Grid.Row="1"
Style="{DynamicResource WrappingImageListBox}"
Background="Transparent"
util:MyAttachedProperties.ImageSize="500"/>
Code Listing 3 - Main Call
This works exactly as I'd like, except for the ImageSize property. Both ImagePath and Selected are properties of the individual list items being bound to the ListBox.
As you can see, I created an attached property to try to pass the value (500), but it doesn't seem to be working. I should note that I think the style I've created is correct because the elements use the default value.
public static class MyAttachedProperties
{
public static double GetImageSize(DependencyObject obj)
{
return (double)obj.GetValue(ImageSizeProperty);
}
public static void SetImageSize(DependencyObject obj, double value)
{
obj.SetValue(ImageSizeProperty, value);
}
public static readonly DependencyProperty ImageSizeProperty =
DependencyProperty.RegisterAttached(
"ImageSize",
typeof(double),
typeof(MyAttachedProperties),
new FrameworkPropertyMetadata(50D));
}
Code Listing 4 - Attached Property
The 50D specified on the last line is applying to the listed control. If I change it, and recompile, the end result changes. But the sent value of 500 I specified in my ListBox Main call (listing 3) is not ever sent. Of course, I would eventually like to change the 500 into a bound property on my view model, but I won't do that until I get it working with an explicit value.
Can someone help me figure out how to send a value from my main ListBox call (listing 3) and apply it to the individual items that are populated by the template? The other properties I have work, but they are a properties of each item in the List I'm binding to the ListBox, whereas ImageSize is not.
EDIT To address First Response
This seems to be working, but it's kind of peculiar. My listbox is now being called like so...
<ListBox ItemsSource="{Binding DirectoryPictures}"
Grid.Row="1"
Style="{DynamicResource WrappingImageListBox}"
Background="Transparent" />
And I've changed my style to the code you suggested...
<tcb:ThumbnailCheckBox ImagePath="{Binding ImagePath}"
IsChecked="{Binding Selected}"
Height="{Binding Path=DataContext.ThumbnailSize, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListBox}}}"
CornerRadius="8"
Margin="10">
My only concern is, now the style is accessing the ViewModel for that control directly rather than receiving a bound value.
Suppose I wanted to use the ListBox again, but on another UserControl whose ViewModel didn't have ThumbnailSize property, but used one by another name?
You see where I'm going with this... the current solution is not very extensible and is limited to the current classes as they are named exactly.
In fact, in a perfect world, I'd like to have variable names for the ImagePath and Selected properties, but that's a different discussion.
It's possible to use FindAncestor. The idea of that is, child traverses through logical tree, and tries to find parent with concrete type (in this case, ListBox), and then accesses attached property. See http://wpftutorial.net/BindingExpressions.html for more binding expressions.
In your ItemTemplate, this is how you could access ThumbnailSize property:
{Binding Path=(util:MyAttachedProperties.ImageSize),
RelativeSource={RelativeSource
Mode=FindAncestor,
AncestorType={x:Type ListBox}}}
Essentially, the question asked here was a little bit opposite, but results are same. "How could items in ListBox access ListBox (attached) properties.

Visual Studio WPF UI Designer with Custom Controls

This is a bit of a weird question:
I have a custom control that inherits from TextBox, and provides "ghost" text - eg it says "Username" in a box until you click inside it, whereupon the "ghost" text disappears, and the user can type in their, in this case, Username.
The "Ghost text" for a control is simply a property in a subclass of TextBox. I then set TextBox.Text to it whenever relevant.
In the Visual Studio WPF XAML preview window (the standard UI design one), I would like to be able to "preview" the "Ghost text" - like when you set the actual text of a textbox, you can see it in the preview, not just when you run the application.
I have tried setting the Text property to the relevant Ghost text in the OnInitialised function, but it doesn't have any effect on the preview.
Where should I be putting code that affects the preview of a control in the designer?
Bonus question: Is there an actual name for what I call "ghost" textboxes? Would be good to know for the future!
Is there an actual name for what I call "ghost" textboxes? Would be god to know for the future!
I have seen this referred to as a "hint" when describing its purpose, or as a "watermark" when describing its appearance. I tend to employ the former, as it describes the function, which is more in line with the WPF design philosophy: the actual presentation is determined by the template, and the conceptual "hint" could be presented differently simply by applying a custom style/template. Why imply that it should be a watermark when someone could choose to present it in another way?
Design-wise, I think you're approaching this the wrong way. I would implement this such a way that controls other than a TextBox could more easily opt in: use attached properties.
I would create a static class, say HintProperties, which declares a couple of attached dependency properties:
Hint - declares the hint content; typically a string, but it need not be. It could simply be an object, akin to the Content property of a ContentControl.
HasHint - a computed, read-only bool property that gets reevaluated when Hint changes, and simply indicates whether a control has a Hint specified. Useful as a Trigger condition to toggle the visibility of a hint presenter in your control template.
Then, provide a custom style for your TextBox (or other control) which overlays a Hint presenter atop the regular content, hidden by default. Add a trigger to reduce the opacity of the hint when the control has keyboard focus, and another to make the hint Visible when Text is an empty string.
If you really want to go all-out, you can throw in HintTemplate and HintTemplateSelector properties.
However, if this seems like overkill, you can simply declare a Hint or Watermark property directly on your derived TextBox class. I would not try to implement this by conditionally changing the Text property, as that would interfere with data binding and, potentially, value precedence.
You can do this in a reusable way using a style which you would typically declare in your App.xaml. In this style you replace the control template with your own implementation and wrap together some controls. Basically you make up the WatermarkTextBox from a normal TextBox with a transparent background and place a TextBlock control with standard text behind the TextBox. The Visibility of this TextBlock is bound to the TextBox using a specific TextInputToVisibilityConverter so it will disappear when the TextBox has text or just has the focus.
While this maybe looks like a lot of code, you define this once and you can reuse this whereever you need, just by setting style of the TextBox
Declaration of some resources
xmlns:c="clr-namespace:YourNameSpace.Converters"
<SolidColorBrush x:Key="brushWatermarkBackground" Color="White" />
<SolidColorBrush x:Key="brushWatermarkForeground" Color="LightSteelBlue" />
<c:TextInputToVisibilityConverter x:Key="TextInputToVisibilityConverter" />
Declaration of the style:
<Style x:Key="SearchTextBox" TargetType="{x:Type TextBox}">
<Setter Property="KeyboardNavigation.TabNavigation" Value="None"/>
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
<Setter Property="AllowDrop" Value="true"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TextBox}">
<Grid Background="{StaticResource brushWatermarkBackground}">
<TextBlock Margin="5,5" Text="Search..."
Foreground="{StaticResource brushWatermarkForeground}" >
<TextBlock.Visibility>
<MultiBinding
Converter="{StaticResource TextInputToVisibilityConverter}">
<Binding RelativeSource="{RelativeSource
Mode=FindAncestor, AncestorType=TextBox}"
Path="Text.IsEmpty" />
<Binding RelativeSource="{RelativeSource
Mode=FindAncestor, AncestorType=TextBox}"
Path="IsFocused" />
</MultiBinding>
</TextBlock.Visibility>
</TextBlock>
<Border x:Name="Border" Background="Transparent"
BorderBrush="{DynamicResource SolidBorderBrush}"
BorderThickness="1" Padding="2" CornerRadius="2">
<!-- The implementation places the Content into the
ScrollViewer. It must be named PART_ContentHost
for the control to function -->
<ScrollViewer Margin="0" x:Name="PART_ContentHost"
Style="{DynamicResource SimpleScrollViewer}"
Background="Transparent"/>
</Border>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
The implementation of the TextInputToVisibilityConverter, which just takes text input, converts to bool and converts this to Visibility. Also keeps Focus into account.
namespace YourNameSpace
{
public class TextInputToVisibilityConverter : IMultiValueConverter
{
public object Convert(object[] values,
Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
if (values[0] is bool && values[1] is bool)
{
bool hasText = !(bool)values[0];
bool hasFocus = (bool)values[1];
if (hasFocus || hasText)
return Visibility.Collapsed;
}
return Visibility.Visible;
}
public object[] ConvertBack(object value,
Type[] targetTypes, object parameter,
System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
Now all infrastructure is into place. In your view/usercontrol/window just alter the style of the Textbox and there it is, your watermark textbox..
<TextBox Style="{DynamicResource SearchTextBox}" />

Passing DataGridRow as Object to a Converter

Once again I have a little problem with WPF, XAML and probably my own stupidity ;)
I have a DataGrid which is bound to the DataContext. The DataContext is an array of Objects from a Class I made myself (something simple like class Employee with Properties like FirstName, LastName, etc... really nothing special.)
I wanted to create a ToolTip for the rows, so I made a RowStyle where I assigned the ToolTip. It was first bound to a Property named 'Status'. The following code worked fine:
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Setter Property="ToolTip">
<Setter.Value>
<TextBlock Text="{Binding Status Converter={StaticResource StatusToolTipConverter}}"/>
</Setter.Value>
</Setter>
</Style>
</DataGrid.RowStyle>
But then I changed my mind. I don't want to pass only the "Status" Property, now I want to pass the whole Object (Remember: one row = one object). Or in other words: I want to Pass the DataContext of the row to my converter. Since the Object that I want to pass is the DataContext itself, there shouldn't be much to change. So I changed it to:
<TextBlock Text="{Binding Converter={StaticResource StatusToolTipConverter}}"/>
Now here is where things start to go wrong. The converter gets 'null' as value. So I deleted the converter and tried it again. The ToolTip was correctly bound to the Object in the Row. I could proof that to myself because the correct Object name was shown in the tooltip. I even overwrote the .ToString() so that the Name of the Employee gets shown as Object name, which it did correctly without the converter.
so TL;DR: Why does the Converter get 'null' as Value, when the Object is bound correctly WITHOUT the converter?
I found a little workaround. I made a property called 'Self' which returns 'this'. The ToolTip is now bound to 'Self'. It works.

Categories