I'm very new to WPF and I'm having difficulties binding a request object with nested objects which was derived from a WSDL to a XAML textbox. Programmatically I was able to bind to a textbox but I would like to understand the syntax needed to bind via XAML. Once I have some direction It'll make it much easier to research a full solution. Thanks
The ResultSet and Message Object will always be [0].
Code
MainWindow()
{
InitializeComponent();
GetMarketingMessagesResponse request = new GetMarketingMessagesResponse();
request = (GetMarketingMessagesResponse)XMLSerializerHelper.Load(request, #"C:\SSAResponse.xml");
DataContext = request;
Binding bind = new Binding();
bind.Source = request.ResultSet[0].Message[0];
bind.Path = new PropertyPath("SubjectName");
this.txtbSubject.SetBinding(TextBox.TextProperty, bind);
}
The return value in the Visual Studio Watch bind.Source = request.ResultSet[0].Message[0];
is
bind.Source = {GetMarketingMessagesResponseResultSetMessage} which is the class name.
XAML
I'm looking for direction on how to bind to this class and the properties inside
<TextBox Name="txtbMessageDetails" HorizontalAlignment="Right" Margin="0,50.08,8,0" TextWrapping="Wrap" Text="{Binding Source=ResultSet[0].Message[0], Path=SubjectName}" VerticalAlignment="Top" Height="87.96" Width="287.942"/>
Use a converter which will receive the request and extract the message.
<Window.Resources>
<local:MessageExtractorConverter x:Key="messageExtractorConverter" />
</Window.Resources>
<TextBox Name="txtbMessageDetails" HorizontalAlignment="Right" Margin="0,50.08,8,0" TextWrapping="Wrap" Text="{Binding Converter={StaticResource messageExtractorConverter}" VerticalAlignment="Top" Height="87.96" Width="287.942"/>
Converter implementation:
public class MessageExtractorConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var val = value as GetMarketingMessagesResponse;
if (val != null)
{
// You can modify this code to extract whatever you want...
return val.ResultSet[0].Message[0];
}
else
{
return null;
}
}
You already put the request object in your DataContext, making it the default Source for all bindings. So, instead of specifying another Source (which would just override the DataContext), you use the binding's Path to make your way from the DataContext to the property you need:
<TextBox Name="txtbMessageDetails" Text="{Binding Path=ResultSet[0].Message[0].SubjectName}" />
Here is an article explaining how the DataContext works, and how it is "inherited" from control to control in your Window: http://www.codeproject.com/Articles/321899/DataContext-in-WPF
Related
I'm trying to dynamically bind an Image Source in XAML to an URI in ViewModel (MVVM). This works fine for the initial URI, the picture "C:\tmp\Test.png" is shown. But if I set another URI to ImageURI in ViewModel the picture is not updated. Can anyone help me?
XAML:
<Image x:Name="UserImage" Stretch="Fill" Grid.Row="0">
<Image.Source>
<BitmapImage CreateOptions="IgnoreImageCache" UriSource="{Binding ImageURI, UpdateSourceTrigger=Explicit, Mode=TwoWay}"/>
</Image.Source>
</Image>
ViewModel:
public string imageURI = "C:\\tmp\\Test.png";
public string ImageURI
{
get
{
return imageURI;
}
set
{
imageURI = value;
this.OnPropertyChanged("ImageURI");
}
}
BitmapImage implements ISupportInitialize. This means that property changes are ignored after initialization. Changing the Binding's source property has no effect.
You should directly bind the Image's Source property. Built-in automatic type conversion will create a BitmapFrame behind the scenes.
<Image Source="{Binding ImageURI}" .../>
Setting UpdateSourceTrigger=Explicit and Mode=TwoWay on the Binding is pointless.
If you need to explicitly create a BitmapImage (e.g. one where the IgnoreImageCache option is set), write an appropriate Binding Converter.
Pretty old question here with no solution. Sooo since i had a similar problem and used a slightly different approach, here is my solution:
Use a converter which returns a BitmapImage in this way:
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
BitmapImage error = new();
error.BeginInit();
// OnLoad will give you errors just with the start of your application and
// and won't hide somewhere in your log
error.CacheOption = BitmapCacheOption.OnLoad;
error.UriSource = new Uri(#"pack://application:,,,/Images/error.png");
error.EndInit();
error.Freeze();
return error;
}
The binding:
<Image Source="{Binding Status, Converter={StaticResource enumAnalyzerStatusConverter}, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" />
The OnLoad option will give an error on startup if something is bad with your image like the path. Remember to set the image in your project explorer to Ressource.
I am working on a knowledgebase application which manages articles.
An article consists of a header (Id, Author ... etc.) and a set of text fragments, one of which contains the Title (TextType==2).
There is a listbox intended to display Id and Title but I have been unable correctly to bind the title to a textblock.
I have working code elsewhere to load a title entity
ArticleText te = _header.ArticleTexts.Where(at => at.TextType == 2).FirstOrDefault();
so the property of the entity yielding the title would be te.Body
I set the ItemsSource of my listbox in code
public ObservableCollection<ArticleHeader> HeaderCollection
{
get
{
return (ObservableCollection<ArticleHeader>)articlesListBox.ItemsSource;
}
set
{
articlesListBox.ItemsSource = value;
}
}
which correctly displays the Id but cannot seem to work out a way to bind to the (lazy loaded) title string.
My (simplified) xaml is as follows
<ListBox Name="articlesListBox">
<ListBox.ItemTemplate>
<DataTemplate>
<DockPanel>
<TextBlock DockPanel.Dock="Left" Text="{Binding Path=Id}"/>
<TextBlock TextWrapping="WrapWithOverflow" Margin="0,0,2,0"/>
</DockPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I am aware there may be a number of solutions, but what is the next step ?
Same as you did for ID?
<TextBlock Text={Binding Path=Body}/>
or
<TextBlock Text={Binding Path=Body.Title} />
Might have misunderstood your issue...
The
ArticleTexts.Where(TextType==2)
part is something that should happen in your viewmodel/controller, not in XAML.
Instead of binding your ListBox to a collection of ArticleHeader objects you can create a new collection of anonymous objects that only hold the information you need (id and title), expose it as a property and bind your ListBox to that.
Edit:
There's always the alternative of using a BindingConverter, but that would entail creating a brand new class, which would be even more of a hassle than creating a new collection. Maybe you should add the solution you have in mind in your question and explicitly ask whether a better alternative exists.
I cannot help thinking there must be other (better) ways, but I was able to achieve what I asked by defining a value converter.
[ValueConversion(typeof(ArticleHeader), typeof(String))]
public class HeaderToTitleConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
ArticleHeader ah = value as ArticleHeader;
ArticleText textEntity = null;
using (KnowledgeManagementEntities ctx = KBContext.NewContext())
{
ctx.ArticleHeaders.Attach(ah);
textEntity = ah.ArticleTexts.Where(at => at.TextType == KBConstants.TITLE_TYPE).FirstOrDefault();
}
if (textEntity == null)
return "";
if (String.IsNullOrEmpty(textEntity.Body))
return "";
return textEntity.Body;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
Then in App.Xaml
<Application x:Class="SupportKB.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="MainWindow.xaml"
xmlns:local="clr-namespace:SupportKB" xmlns:Properties="clr-namespace:SupportKB.Properties">
<Application.Resources>
<ResourceDictionary>
<local:HeaderToTitleConverter x:Key="headerToTitleConverter" />
</ResourceDictionary>
</Application.Resources>
</Application>
so the xaml now looks like this
<DataTemplate>
<DockPanel Margin="0,2">
<TextBlock DockPanel.Dock="Left" Text="{Binding Path=Id}" Width="50" />
<TextBlock TextWrapping="WrapWithOverflow"
Margin="0,0,2,0"
Text="{Binding Converter={StaticResource headerToTitleConverter}}"/>
</DockPanel>
</DataTemplate>
In the end, though, I am going to rework the design so that a list of titles is master, and the headers are selected from that.
In WPF you can use an IValueConverter or IMultiValueConverter to convert a data-bound value from say an int to a Color.
I have a collection of Model objects which I would like to convert to their ViewModel representations but in this scenario,
<ListBox ItemsSource="{Binding ModelItems,
Converter={StaticResource ModelToViewModelConverter}" />
the converter would be written to convert the whole collection ModelItems at once.
I wish to convert the items of the collection individually, is there a way to do that? I might want to use a CollectionViewSource and have some filtering logic so I don't want to have to iterate over the whole collection every time something changes.
You cannot set the converter on the collection itself, because it would get the collection as input. You have two choices:
Make sure your converter can also deal with collections (IEnumerable).
Use the converter within the item template.
If you want to use the second approach, then use something like this:
<ListBox ItemsSource="{Binding ModelItems}">
<ListBox.ItemTemplate>
<DataTemplate>
<ContentPresenter Content="{Binding Converter={StaticResource ModelToViewModelConverter}}"
ContentTemplate="{StaticResource MyOptionalDataTemplate}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
If you don't need a custom datatemplate, then you can skip the ContentTemplate attribute.
Yes you can. It is acting the same as with the IValueConverter. You simply treat the value parameter for the Convert method as a collection.
Here is an example of Converter for a collection:
public class DataConvert : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
ObservableCollection<int> convertible = null;
var result = value as ObservableCollection<string>;
if (result != null)
{
convertible = new ObservableCollection<int>();
foreach (var item in result)
{
if (item == "first")
{
convertible.Add(1);
}
else if (item == "second")
{
convertible.Add(2);
}
else if (item == "third")
{
convertible.Add(3);
}
}
}
return convertible;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
In this case is just a proof of concept, but I think it should show the idea very well.
The Converter converts from a simple collection of strings like this:
ModelItems = new ObservableCollection<string>();
ModelItems.Add("third");
ModelItems.Add("first");
ModelItems.Add("second");
into a collection of integers corresponding to the string meaning.
And here is the corresponding XAML (loc is the reference of the current assembly where is the converter):
<Window.Resources>
<loc:DataConvert x:Key="DataConverter"/>
</Window.Resources>
<Grid x:Name="MainGrid">
<ListBox ItemsSource="{Binding ModelItems, Converter={StaticResource DataConverter}}"/>
</Grid>
If you want to make a two way binding, you have to implement also the convert back. From the experience of working with MVVM, i suggest to use something like the Factory Pattern to transform from Model in ViewModel and backwards.
Here is another example. I'm using MVVM Caliburn Micro. MyObjects is a list of enums in my case.
<ListBox x:Name="MyObjects">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ., Converter={StaticResource MyConverter}}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I'm binding an ObservableCollection to a control which has a converter to change its visibility depending on if the collection has any values or not:
Simplified example:
XAML:
<Window.Resources>
<local:MyConverter x:Key="converter"/>
</Window.Resources>
<Grid x:Name="grid">
<Rectangle Height="100" Width="200" Fill="CornflowerBlue"
Visibility="{Binding Converter={StaticResource converter}}"/>
<Button Content="click"
HorizontalAlignment="Left" VerticalAlignment="Top"
Click="Button_Click"/>
</Grid>
C#:
ObservableCollection<string> strings;
public MainWindow()
{
InitializeComponent();
strings = new ObservableCollection<string>();
grid.DataContext = strings;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
strings.Add("new value");
}
When the collection is bound, the Rectangle is visible when there are values and not when the collection is empty. However, if the collection is empty and I add a value at runtime, the Rectangle does not appear (the converter's Convert method isn't even fired). Am I missing something or just trying to ask too much of IValueConverter?
OK, so here's how I got around the problem using a MultiValueConverter
The converter now looks like:
public object Convert(
object[] values,
Type targetType,
object parameter,
System.Globalization.CultureInfo culture)
{
ObservableCollection<string> strings =
values[0] as ObservableCollection<string>;
if (strings == null || !strings.Any())
return Visibility.Collapsed;
else
return Visibility.Visible;
}
public object[] ConvertBack(
object value,
Type[] targetTypes,
object parameter,
System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
And the XAML now looks like:
<Rectangle Height="100" Width="200" Fill="CornflowerBlue">
<Rectangle.Visibility>
<MultiBinding Converter="{StaticResource converter}">
<Binding Path="."/>
<Binding Path="Count"/>
</MultiBinding>
</Rectangle.Visibility>
</Rectangle>
The C# remains the same :)
I think the converter in a Binding is always called if the Binding source has been updated and notifies about that update (as a DependencyProperty or using INotifyPropertyChanged). However, an ObservableCollection does not raise the PropertyChanged event if an item has been added or removed, but it raises the CollectionChanged event. It does not raise any event at all if an item in the collection is changed. Even if the item itself raises PropertyChanged, this will not update the Binding on the collection since the Binding source is not the item, but the collection.
I fear your approach will not work this way. You could bind directly to ObservableCollection.Count and add an appropriate math converter to it to perform the inversion and multiplication, but the Count property does not perform change notification, so this no option. I think you will have to provide another property in your ViewModel or code-behind which handles these cases...
best regards,
You must set the DataContext after creating the collection; it is probable that you initialize the "strings"collection to "null", you set the DataContext in the constructor to that value(e.g. null), then you actually create the collection--this way, the DataContext remains null.
You must set the DataContext again after creating the collection.
Is there a way to limit the number of rows that get displayed in items control. ?
I have a observable collection of strings which are bound to Items control. I want to limit the number of rows to display to only one. The collection can have more than one.
Thanks,
You can use a IValueConverter for this:
public class ItemsLimiter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
int count;
if (Int32.TryParse((string)parameter, out count))
{
return ((IEnumerable<object>)value).Take(count);
}
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
return null;
}
}
In XAML you can use it like this:
<ItemsControl ItemsSource="{x:Bind Data, Converter={StaticResource ItemsLimiter}, ConverterParameter=12}">
Let's say your ItemsSource is set to MyObservableCollection.
Well, what if you change your ItemsSource so that it is pointing a MyOneItemCollection instead?
Then, just use LINQ to do something like this:
using System.Linq;
MyOneItemCollection = MyObservableCollection.First();
or
using System.Linq;
MyOneItemCollection = MyObservableCollection.Single(item => item.Id = MyId);
If you only ever need one item to display you can show the first item using a ContentControl instead with the same available templating options:
<ContentControl DataContext="{erm:Items FieldName}" Content="{Binding [0]}">
<DataTemplate>
<TextBlock Text="{Binding}" TextWrapping="Wrap" FontSize="14" VerticalAlignment="Center" FontWeight="Bold" />
</DataTemplate>
</ContentControl>
Check out my PaginatedObservableCollection here http://jobijoy.blogspot.com/2008/12/paginated-observablecollection.html
This is a subclassed observableCollection which lets you bind to N items and make the UI display 'n' items when you set ItemsPerPage. In your case if you put 1 and can bind the next and previous also to some buttons as in my sample.
Hope this give you some idea.
You can implement a custom CollectionView that only provides n elements. I did something similar in my autocomplete textbox control implementation, check it out here:
A Reusable WPF Autocomplete TextBox
Scroll down to the header titled Limiting the Completions List see what I did there.
You can do this by creating a CollectionView that produces only a single item. This is quite simple: Just subclass CollectionView and override OnCollectionChanged to set a filter to filter out everything except the first item in the underlying collection:
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs args)
{
base.OnCollectionChanged(args);
var first = SourceCollection.FirstOrDefault();
Filter = (obj) => obj == first;
}
Now you need to insert this CollectionView into your ItemsSource. If erm:Items's ProvideValue produces the actual collection or a Binding with no Converter, you can just create your own MarkupExtension which either wraps it in your custom view or adds a Converter to do the wrapping. On the other hand, if erm:Items produces a Binding that already has a Converter or you can't rely on knowing what it produces, you should probably use a more general solution - I would suggest attached properties.
To use attached properties, your ItemsControl will be bound like this:
<ItemsControl
my:SinglerCollectionViewCreator.ItemsSource="{erm:Items FieldName}"
... />
and the code in the SinglerCollectionViewCreator class would be:
public class SinglerCollectionViewCreator : DependencyObject
{
public object GetItemsSource(... // use "propa" snippet to fill this in
public void SetItemsSource(....
public static readonly DependencyProperty ItemsSourceProperty = ...
{
PropertyChangedCallback = (obj, e)
{
obj.SetValue(ItemsControl.ItemsSourceProperty,
new SinglerCollectionView(e.NewValue));
}
}
}
The way this work is, whenever your new SinglerCollectionViewCreator.ItemsSource property is set on any object, the value is wrapped inside your SinglerCollectionView class and the ItemsControl.ItemsSource is set on the same object.
Is there a way to do this entirely in XAML, without writing code?
<ItemsControl Height="100" ItemsSource="{erm:Items FieldName}" Grid.Row="1" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" TextWrapping="Wrap" FontSize="14" VerticalAlignment="Center" FontWeight="Bold" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
I have the items source bound like above. The Items markup extension returns observable collection of strings and I have no control over the observable collection.