Customize text of TextBlock Text within ListBox dynamically - c#

I have a ListBox with a custom DataTemplate as follows:
<ListBox>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Value}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I would like to customize the TextBlock contents based on the value of another control on the page which is used to filter the items in the ListBox. So if the filter control had the text "Hello" in it, a list item with text of "Hello World!" would appear as Hello world! (with "Hello" bolded).
I am not sure where to hook in this type of custom formatting. I thought about using a Converter, but they only support a single parameter and multivalueconverters are a no-go still in Silverlight 4. I thought about an event where I could iterate through the items, but none seems to be present in the ListBox or at the Item level. I saw this option, but I have to wonder if there is not a simpler solution to this problem.
UPDATE: This is even make more complicated by the fact that I will need to use multiple RUN blocks since matches can occur in multiple locations within a string. E.g. Hello world Hello would have two matches.

I think you can accomplish this with a converter. You would just pass in the value of the filter textbox in the ConverterParameter. Your binding would look something like this:
<TextBlock Text="{Binding Value, Converter={StaticResource YourConverterName}, ConverterParameter={ElementName=FilterTextBox, Path=Text}}" />
Convert method for reference:
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
At that point, in your Convert method, you would have the text of the TextBlock via the value parameter, and the text of the filter TextBox via the "parameter" parameter.

Related

How to sort ItemsSource dynamically in C#; WPF

I use in XAML an ItemsControl where and in its ItemsSource I make a Binding for an Enum, thus creating a list of RadRadioButton dynamically, and if someday another item is added to this enumerator my code will already create this new button and show it.
<ItemsControl ItemsSource="{Binding MyEnum}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<telerik:RadRadioButton GroupName="GroupMyEnum">
<TextBlock Text="{Binding Converter={StaticResource EnumDescriptionConverter}}"/>
</telerik:RadRadioButton>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Today I use a converter that takes the description of the Enum and shows instead of the value of the Enum.
But besides that I would like to change the order in which my list of buttons is generated, is this possible?
Example: If my list needs to be generated elsewhere in the interface, it must be generated in a different order than the enumerator was created.
Whether an Enum has the options A, B, C, D. At one point I would like to show as the first option D instead of A.
Was I clear enough?
Define a converter that takes MyEnum, change its order based on the value of converter's parameter and return the new list with the new order (ConverterParameter is optional, do not set it if you don't want to change the order of the list).
public class MyEnumCollectionConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is IEnumerable<MyEnum> input)
{
switch (parameter)
{
case "Case1":
// todo: change the order of input
return input;
case "Case2":
// todo: change the order of input
return input;
}
}
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Define the converter in your app.xaml (or at one of ItemsControl parents) and use it like this..
<ItemsControl ItemsSource="{Binding MyEnum, Converter={StaticResource MyEnumCollectionConverter}, ConverterParameter=Case1}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<telerik:RadRadioButton GroupName="GroupMyEnum">
<TextBlock Text="{Binding Converter={StaticResource EnumDescriptionConverter}}"/>
</telerik:RadRadioButton>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Note that you can do the EnumDescriptionConverter work inside MyEnumCollectionConverter, so your TextBlock can be just
<TextBlock Text="{Binding .}"/>
The first thing that pops to mind is sorting the ItemSource itself. I believe you use Linq sort by calling List.Sort() on the source itself and the list of order on the visual layer should be altered aswell.
More information about this can be found here.

ComboBox Text ValueConverter Lag - C# WPF XAML

I currently have a combobox in my WPF application, but it seems like there's some lag before the value is converted.
The value converter converts a UUID/GUID to a name, but what shows up on the combobox is first the UUID/GUID for a split second and then the name shows up.
Here's a boiled down version of my combobox:
<ComboBox x:Name="MyComboBox"
Text="{Binding Path=GUID, Converter={StaticResource GUIDToNameValueConverter}, Mode=OneWay}"
ItemTemplate="{StaticResource MyTemplate}"
Style="{StaticResource MyStyle}"
ItemsSource="{Binding Source={x:Static myNameSpace:MyItems}, Path=Items}"
SelectionChanged="MyChangedEventHandler">
</ComboBox>
Here's the general gist of my valueconverter:
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
string GUID = (string)value;
MyObject myObject = GetObjectById(GUID);
return myObject.name;
}
Essentially, I want to get rid of the split second display of the GUID before the actual name shows up. I'm unsure why the GUID event shows up since the converter takes care of converting that to a name.
Let me know if this is enough information to have this question answered, if not, please request more parts of my code!

Windows Phone 8.1 Listview value mapping

I have a Listview control in a XAML page which has it's ItemSource bound to a collection in the code behind.
The bound data model within the collection that is bound to each ListView item has one integer field which can contain the value 0 or 1.
I am binding this field to a TextBlock in the ItemTemplate of the ListView, and so in the ListView I am seeing rows containing the text 0 or 1.
My goal is to see "sometext1" instead of 0 and "sometext2" instead of 1 in the ListView without changing the ItemSource itself.
The problems I am seeing are:
TextBlock controls seems to unable to override from code to create a custom TextBlock in which I can change values programatically.
If I use TextBox control instead, I can change the values, but the program slows down when large amount of data is shown and it also not shown the changed values. (In debug mode I can see, that text property has the new value, but the TextBox is empty on screen.)
This is where developing for the Windows platform really is easiest if you do things in the "Windows" way. This involves taking advantage of the awesome functionality built into the XAML presentation framework like binding and converters!
I'm going to ignore that the data comes from a database, as it could come over the network or created programmatically and it shouldn't matter, bottom line is it exists in memory somewhere.
Assuming you have a model class for your data:
class Data
{
public int intField { get; set; }
public string otherField { get; set; }
}
and in the code behind of your page with the listview, an collection of data objects:
private ObservableCollection<Data> ListViewData = new ObservableCollection<Data>();
We can then bind your ListView data to this collection in the XAML page as I'm assuming you have done:
<ListView ItemsSource="{Binding ListViewData}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<TextBlock x:Name="tbIntField" Text="{Binding intField, Mode=OneWay}"/>
<TextBlock x:Name="tbOtherField" Text="{Binding otherField, Mode=OneWay}"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
As you have observed, at this point, the contents of the intField TextBlock will be the numeric value of the intField which isn't what you want. So we're going to use a converter to format the TextBlock contents based on the value of the intField.
First, create a new class in your project, I called it intFieldConverter which implements the IValueConverter interface:
class intFieldConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
var intField = (int)value;
switch (intField)
{
case 0:
return "Foo";
case 1:
return "Bar";
default:
return default(string);
}
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
All this does is takes the input int value and returns the string you want to show based on value. I didn't implement the method to convert it back but that's just the same but backwards; you can do that if you need two way binding.
We now need to tell your XAML about this converter. Add your converter namespace to your XAML page if you need it:
<Page
....
xmlns:converters="using:BindingTest.UI.Converter"
....>
We then need to specify the Converter resource in that converters namespace inside the Page tag:
<Page.Resources>
<converters:intFieldConverter x:Key="customIntToStringConverter"/>
</Page.Resources>
We can now use it in our TextBlock binding to convert the values and show the right string in the view:
<TextBox x:Name="tbIntField" Text="{Binding intField, Mode=OneWay, Converter={StaticResource customIntToStringConverter}}"/>
If your codebehind has the observable collection populated with some data, you should now see each row in the listview will now show either "Foo" or "Bar" based upon the value of the intField in the data.
Hope this Helps
Note:
I haven't run any of this code to test it as I don't have a machine to test it on with me at the moment. I can check when I get home to double check if you haven't been able to get it working before.

How to assigning Datacontext as well as static value in a single Combobox

I would like to assign datacontext and static values in a single Combobox. I tried like this,
this.courseList.DataContext = ldc.Courses.OrderBy(x => x.CourseID);
this.courseList.Items.Add("All");
this.courseList.Items.Add("Others");
But, it shows InvalidOperationException was Unhandeled that Items collection must be empty before using ItemsSource.
Then I tried like this,
this.courseList.Items.Add("All");
foreach (var items in ldc.Courses.OrderBy(x => x.CourseID))
{
this.courseList.Items.Add(items);
}
this.courseList.Items.Add("Others");
It works like this Image
Since I used an ItemTemplate in XAML design for this Combobox so it doesn't shows exactly in my way for this two value All and Others.
Here is the ItemTemplate
<DataTemplate x:Key="CourseTemplate">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=CourseID}"/>
<TextBlock Text=" : "/>
<TextBlock Text="{Binding Path=CourseName}"/>
</StackPanel>
</DataTemplate>
...
<ComboBox Name="courseList" VerticalAlignment="Top" SelectionChanged="courseList_SelectionChanged" IsSynchronizedWithCurrentItem="True" ItemsSource="{Binding}" ItemTemplate="{StaticResource CourseTemplate}"/>
I want it to view according to DataTemplate for database entries and simple strings All and Others for All and Others respectively like the image below.
Any suggestions. Thank you.
As the error says, you can only use ItemsSource or Items directly, not a mix of the two. Since you essentially have two different types of data that you want to show differently you can add them all to a single bound collection and then let the templates do the work of rendering them differently. Step 1 is to build the single collection:
this.courseList.ItemsSource = new object[] { "All" }.Concat(ldc.Courses.OrderBy(x => x.CourseID)).Concat(new[] { "Others" });
You now have 2 types of data in the collection: String and your object which I'll assume is Course. The strings will show up as you want without any extra work but you need to apply the DataTemplate only to the Course objects. The simplest way is just to replace your x:Key with a DataType:
<DataTemplate DataType="{x:Type data:Course}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=CourseID}"/>
<TextBlock Text=" : "/>
<TextBlock Text="{Binding Path=CourseName}"/>
</StackPanel>
</DataTemplate>
where the data xmlns corresponds to your Course object's namespace.
Next just remove the ItemTemplate attribute from your ComboBox and you should get what you're looking for.
If you have other places in the same scope where you're also binding Course objects but want them to display differently you can either scope the DataType template more locally (i.e. a Grid.Resources) or use a DataTemplateSelector on your ComboBox.
You can solve this by using ItemTemplate combined with converter.
Template:
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={StaticResource ComboBoxItemConverter}}" />
</DataTemplate>
</ComboBox.ItemTemplate>
Converter:
public class ComboBoxItemConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value is Courses)
{
var course = value as Courses;
return course.CourseID + " : " + course.CourseName;
}
else
{
return value.ToString();
}
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Since ldc.Courses is set as the DataContext, and you have ItemsSource="{Binding}" this means that the combobox is bound directly to your collection. This creates a problem when you try to add strings as you did since now the collection has both strings and Course objects.
I think this will probably be the easiest solution to understand. Starting with your second approach:
this.courseList.Items.Add("All");
foreach (var items in ldc.Courses.OrderBy(x => x.CourseID))
{
this.courseList.Items.Add(items.CourseID.ToString() + " : " + items.CourseName);
}
this.courseList.Items.Add("Others");
This will keep your combobox bound to a simple list of strings instead of objects, so as long as you don't need other information in the background this will be simpler to digest, if not try one of the other answers that mixes strings and objects into the same collection. This would also mean you don't need to set the DataContext, the ItemsSource, or the DataTemplate, so erase all that.
If you want to keep the template to add a little more control, again you may want to look at some of the other answers. Another alternative would be to create a parent class over your courses object that could also take the more general ALL and OTHER cases and create actual objects around them probably the most flexibility. This way you wouldn't end with a collection of both strings and objects, which will cause you to have to create special code cases anytime you manipulate the collection.

Bind textblock to dictionary value for key in XAML?

I have a model with an enum property (in this case, related to Export Control Regulations). When displaying the value to the user, I want to show a corresponding string. Sometimes this is in ComboBox (where the user can select a value), and sometimes it is in a TextBlock (where it is read-only).
Example: for ExportRegulationType.EAR, I want to display "EAR", while for ExportRegulationType.DoNotExport, I want to display "Do Not Export". Note that I don't have any language localization needs, but I recognize the issue...
Currently, in my ViewModel, I have a property that returns a string based on the current enum value, and also another property that returns a Dictionary<ExportRegulationType, string>. For the ComboBoxes, I can bind ItemsSource to the dictionary property, and for the TextBlocks, I can bind to the string property. This works, but is kind of clumsy.
Two questions:
1) It seems to me that I should be able to declare the dictionary (with keys and values) as a static resource in XAML (probably in App.xaml), and use that for the ItemsSource for the ComboBox version. However, I can't figure out how to declare and reference such a thing. How can I do that?
2) Assuming the above is in place, I would think I could also set up a binding with the textblock, so based on the enum property, it will look up the string in the dictionary.
I have seen the following questions relating to a static or dynamic enum value. The first isn't adequate, and the second isn't answered...
These should be a XAML-only, and will enable me to remove the methods from my ViewModel (having only the one exposed ExportRegulationType enumerated property. Are these possible?
Edit: Additional information:
In the application, I will have many different sets of views, models, and ViewModels. However, as export control regulations are a common and consistent requirement, I am using composition to keep it DRY. i.e., Models A and B both have an ExportControl model. ViewModels A1, A2, B1 and B2 will have an ExportControlViewModel. The views will have controls bound to the ExportControlViewModel of their ViewModel. The views will have either a ComboBox or a TextBlock, but not both (Depending on if the user can change the value).
I don't know if this will work for your case, but here is a possible solution. In your view model, expose a ExportRegulationType property and then create a value converter to display your desired string.
First create your value converter:
class ExportRegulationTypeToStringConverter: IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
ExportRegulationType regType = (ExportRegulationType)value;
switch(regType)
{
case ExportRegulationType.EAR:
return "EAR";
case ExportRegulationType.DoNotExport:
return "Do Not Export";
//handle other cases
}
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return Binding.DoNothing;
}
#endregion
}
Then add a reference to your converter in your xaml. local is the namespace in which your class is located.
<local:ExportRegulationTypeToStringConverter x:Key="exportRegConverter" />
Finally, set the value of your text box to use the converter. pathToEnum is the property exposed on your ViewModel of type ExportRegulationType.
<TextBlock Text="{Binding pathToEnum, Converter={StaticResource exportRegConverter}}" />
Use ObjectDataProvider to fill the ComboBox with the values of the enum.
<Window.Resources>
<ObjectDataProvider x:Key="dataFromEnum"
MethodName="GetValues" ObjectType="{x:Type System:Enum}">
<ObjectDataProvider.MethodParameters>
<x:Type TypeName="local:ExportRegulationType"/>
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
</Window.Resources>
Now we create the ComboBox and use a container style with our value converter to display the desired strings for our enum.
<ComboBox ItemsSource="{Binding Source={StaticResource dataFromEnum}}">
<ComboBox.ItemContainerStyle>
<Style TargetType="ComboBoxItem">
<Setter Property="Content" Value="{Binding Converter={StaticResource exportRegConverter}}" />
</Style>
</ComboBox.ItemContainerStyle>
</ComboBox>
Instead of the Dictionary you have another option.
See the following question: WPF Binding a ListBox to an enum, displaying the Description Attribute
You could add a Description Attribute to your enums like this
public enum ExportRegulationType
{
[Description("EAR")]
EAR,
[Description("Do Not Export")]
DoNotExport
}
And when you want to display it, you can just use EnumDescriptionConverter Converter found in the question I linked
I solved this with a blend of what #Dylan and #Meleak wrote. I'm putting this as an answer to show what the final solution was:
First, I implemented an IValueConverter, (based on #Meleak's answer):
class EnumDescriptionConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
Enum regulation = (Enum)value;
return GetEnumDescription(regulation);
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return String.Empty;
}
/// <summary>
/// Returns text intended for display based on the Description Attribute of the enumeration value.
/// If no Description Attribute is applied, the value is converted to a string and returned.
/// </summary>
/// <param name="enumObj">The enumeration value to be converted.</param>
/// <returns>Text of the Description Attribute or the Enumeration itself converted to string.</returns>
private string GetEnumDescription(Enum enumObj)
{
// Get the DescriptionAttribute of the enum value.
FieldInfo fieldInfo = enumObj.GetType().GetField(enumObj.ToString());
object[] attributeArray = fieldInfo.GetCustomAttributes(typeof(DescriptionAttribute), false);
if (attributeArray.Length == 0)
{
// If no Description Attribute was found, default to enum value conversion.
return enumObj.ToString();
}
else
{
// Get the text of the Description Attribute
DescriptionAttribute attrib = attributeArray[0] as DescriptionAttribute;
return attrib.Description;
}
}
}
I tagged my enum (note that several values are not tagged as the desired text is the same as the value itself):
public enum ExportRegulationType
{
[Description("Not Determined")]
NotDetermined, // Export authority not determined
EAR, // Controlled by EAR Regulations
ITAR, // Controlled by ITAR Regulations
[Description("Do Not Export")]
DoNotExport, // Export not allowed
Unrestricted // Export not controlled
}
In my App.xaml, I declared the ObjectDataProvider to get the list of enum values and the EnumDisplayConverter (Here since they will be used by several different views):
<Application.Resources>
[Other stuff...]
<ObjectDataProvider MethodName="GetValues"
ObjectType="{x:Type sys:Enum}"
x:Key="ExportRegulationValues">
<ObjectDataProvider.MethodParameters>
<x:Type TypeName="models:ExportRegulationType"/>
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
<local:EnumDescriptionConverter x:Key="ExportDisplayConverter"/>
</Application.Resources>
For a TextBlock:
<TextBlock Text="{Binding Export.Regulation, Converter={StaticResource ExportDisplayConverter}}"/>
For a Combo Box:
<ComboBox ItemsSource="{Binding Source={StaticResource ExportRegulationValues}}"
SelectedValue="{Binding Document.Export.Regulation}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={StaticResource ExportDisplayConverter}}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
This works perfectly!
Use an ObjectDataProvider
then bind the ComboBox's Items to it, and set "DisplayMemberPath" to "Value".
What this should do is to show the values of your dictionary, but in code-behind the SelectedValue is a KeyValuePair<>.
For your textblock, use a Binding using ElementName=yourcombobox and Path=SelectedItem:
<TextBlock Text="{Binding SelectedItem, ElementName=yourcombobox}" />
Let me know how it goes =)
Here is a blog post of mine with an approach using attached behaviors.
It is based on the principle that different enumeration values don't need to be limited to switching strings. Instead, you can declare whatever pieces of UI you want to represent each value (strings, images, different controls and layouts, etc.) and use an attached behavior to control their visibility.
Your situation, then, can be framed as having two different text blocks, each bound to the same property of type ExportRegulationType. Since they are bound to the same property, their visibilities are mutually exclusive:
<Grid>
<TextBlock
Text="EAR"
local:EnumVisibility.Value="{Binding ExportRegulationType}"
local:EnumVisibility.TargetValue="EAR"
/>
<TextBlock
Text="Do Not Export"
local:EnumVisibility.Value="{Binding ExportRegulationType}"
local:EnumVisibility.TargetValue="DoNotExport"
FontWeight="Bold"
/>
</Grid>
I included the FontWeight="Bold" to show that you can make different decisions for each enumeration value. This also supports XAML localization because the text is set like any other text block.
See the post for a complete walkthrough of the solution, code samples, and a zip file containing the framework and an example application.
Edit in response to additional information:
Here is another post in the same series which describes how to select enumeration values with Selector controls.
A ComboBox bound to the ExportRegulationType property would look this this:
<ComboBox local:EnumSelector.SelectedValue="{Binding ExportRegulationType, Mode=TwoWay}">
<ComboBoxItem Content="EAR" local:EnumSelector.ItemValue="EAR" />
<ComboBoxItem Content="Do Not Export" local:EnumSelector.ItemValue="DoNotExport" />
</ComboBox>
We associate each item with an enumeration value, then use a TwoWay binding to EnumSelector.SelectedValue so it will write back to the view model's property whenever it changes.
This provides the same flexibility as with the text blocks: you can make whatever decisions you want about how to set the text and what is contained by each item.

Categories

Resources