I've used binding many times but this caught me by surprise.
In resources of TabControl I have defined:
<DataTemplate DataType="{x:Type local:ScreenViewModel}">
<Grid>
<Grid.Resources>
<local:converter x:Key="conv"/>
</Grid.Resources>
<di:DisplayView Ime="{Binding Name, Converter={StaticResource conv}, Mode=OneWay}"/>
<TextBlock Text="{Binding Name, Converter={StaticResource conv}, Mode=OneWay}"/>
</Grid>
</DataTemplate>
with converter defined as :
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value;
}
just to debug binding process.
di:DisplayView has Ime dependency property of type string and
local:ScreenViewModel class with INotifyPropertyChanged implemented has Name property which I would like to bind to.
Now, the problem with this code is that when I put breakpoint in Convert method of "conv" I see breakpoint is hit twice. First there is predefined string value set in Name and the next time there is empty string. Even if I remove TextBlock binding i still get empty string as value but if I remove di:DisplayView then everything works fine.
I should mention that is UserControl defined in another namespace as ClassLibrary.
Strange behaviour of WPF, isn't it?
Related
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.
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!
So I have a ProgressRing and a TextBlock and I am trying to implement this basic hack, which is to display both elements when TextBlock's Text gets assigned a value (anything other than null), else both elements should hide when TextBlock's Text is null.
My Xaml looks like below. TextBlock's Text is binded to MessageForProgressRing and its Visibility is binded to both MessageForProgressRing and TargetNullValue. Same for me ProgressRing:
<StackPanel Panel.ZIndex="100" Grid.Column="1" HorizontalAlignment="Center" VerticalAlignment="Center">
<mahControls:ProgressRing Height="50" IsActive="True" Width="50" Visibility="{Binding MessageForProgressRing, TargetNullValue=Collapsed, FallbackValue=Visible}" Foreground="White" Margin="0,0,0.2,0" />
<TextBlock Text="{Binding MessageForProgressRing}" Visibility="{Binding MessageForProgressRing, TargetNullValue=Collapsed, FallbackValue=Visible}"/>
</StackPanel>
Then, in code behind I just trigger the property and assign it a value on some button event handlers:
private void closeApplicationButtonTask()
{
((CaptureViewModel)DataContext).MessageForProgressRing = "Closing... ";
Application.Current.MainWindow.Close();
}
However, in my ViewModelBase (the parent of all my view models) it pops an error on OnPropertyChanged saying:
Requested value 'Closing...' was not found.
I think I need a converter because Visibility is binded to Closing... right? If yes how can I achieve it?
P.S I couldn't do it in OnPropertyChanged because I don't see the value to assign it. Also I don't think it is a good idea since it gets called big time before, during and after the execution.
I usually prefer to solve this problem by having a boolean property in my view model (e.g. HasMessageForProgressRing or IsProgressRingVisible). It's usually a more general-purpose solution. Then you can use a BooleanToVisibilityConverter.
If you truly want to implement a converter, just create a class that implements IValueConverter. The Convert implementation of this should be a piece of cake for your simple use case. ConvertBack isn't necessary in most cases (and won't be in yours). It would look something like this:
public class NullToCollapsed : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value != null ? Visibility.Visible : Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
I have created a multilingual WPF application using Andre's answer from here. I'm binding text like this
<TextBlock Text="{DynamicResource Create}"/>
and can switch from english to french at runtime - nice! However, this does not work with ItemsSource. For example, I have a ComboBox that should display all available languages:
<ComboBox ItemsSource="{Binding AllLanguages, Source={StaticResource Locator}}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{DynamicResource LanguageId}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Set up like this, the combobox displays no text. If I set the textblock's text inside to Text={LanguageId}, I see the LanguageIds 'eng', 'fr' etc., so the binding works.
When using a converter:
<TextBlock Text="{Binding LanguageId, Converter={StaticResource DynamicResourceConverter}"/>
languages are displayed as "English" and "French". When I switch the language, however, the converter is not called again and the language names are not updated - so that is no real workaround.
I'd be very thankful for a tip on the cause and how to fix this.
I'll explain first why a few things are not working.
....
<DataTemplate>
<TextBlock Text="{DynamicResource LanguageId}"/>
</DataTemplate>
....
This is a short hand for Text="{DynamicResource ResourceKey='LanguageId'}" which is a static string literal and does not involve any binding.
It would be great if the following was available, but unfortunetly is NOT POSSIBLE because the target for the binding is not a DependancyProperty.
....
<DataTemplate>
<TextBlock Text="{DynamicResource ResourceKey={Binding LanguageId}}"/>
</DataTemplate>
....
You are close with your workaround. My suggestion would be to try the following:-
....
<DataTemplate>
<TextBlock>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource DynamicResourceConverter}">
<Binding Path="LanguageId"/>
<Binding Path="SomeOtherPropertyThatChangesWhenLanguageIsSwitched" Source="{StaticResource Locator}"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
....
You will need to expand the DynamicResourceConverter to now implement also IMultiValueConverter. In a MultiBinding scenario, if either bound expression changes then the converter is called again. You would write the Converter such that it only operates on values[0] of the supplied object array as the second value is not needed and only provided a trigger for the converter to be called again.
public class DynamicResourceConverter: IValueConverter, IMultiValueConverter
{
....
// original converter implementation for IValueConverter
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
....
}
// newly added converter implementation for IMultiValueConverter
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
//call the original converter method with one value (assuming you've checked the array has at least one item!!
return Convert(values[0], targetType, parameter, culture)
}
....
}
I am trying to bind a 2D array of buttons arranged in stackpanels to a 2D ObservableCollection...
Yet, I'm afraid I don't understand something very elementary about binding.
My XAML:
<Window.Resources>
<DataTemplate x:Key="ItemsAsButtons">
<Button Content="{Binding}" Height="100" Width="100"/>
</DataTemplate>
<DataTemplate x:Key="PanelOfPanels">
<ItemsControl ItemsSource="{Binding Path=DayNumbers}" ItemTemplate=" {DynamicResource ItemsAsButtons}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</DataTemplate>
</Window.Resources>
...
<ItemsControl x:Name="DaysPanel" Grid.ColumnSpan="7" Grid.Row="2"
ItemTemplate="{DynamicResource PanelOfPanels}"/>
My C# code:
The backend:
/// <summary>
/// Window BE for Calendar.xaml
/// </summary>
public partial class Calendar : Window
{
private CalendarViewModel _vm;
public Calendar()
{
InitializeComponent();
_vm = new CalendarViewModel();
this.DataContext = _vm;
}
}
The ViewModel:
class CalendarViewModel
{
CalendarMonth _displayedMonth;
EventCalendar _calendar;
public CalendarViewModel()
{
_displayedMonth = new CalendarMonth();
}
public ObservableCollection<ObservableCollection<int>> DayNumbers
{
get
{
return _displayedMonth.DayNumbers;
}
}
}
I'm trying to populate the buttons with values from CalendarViewModel.DayNumbers - yet the buttons do not appear. I'm clearly doing something wrong with my binding.
Change all your DynamicResource to StaticResource. This shouldn't stop it working, but might be inefficient at runtime. Have a look this page for WPF resources overview.
Also your ItemsControl is not bound to DayNumbers. Add a binding like so:
<ItemsControl x:Name="DaysPanel" Grid.ColumnSpan="7" Grid.Row="2"
ItemTemplate="{StaticResource PanelOfPanels}"
ItemsSource={Binding DayNumbers}/>
When you set the DataContext on Calendar window you set which object will be the default binding source for the whole window. You didn't specify which property of your ViewModel is bound to the ItemsControl. This is what the code above does.
EDIT Because you are overriding the item template for the ItemsControl and provide a collection container there, you need to provide the ItemsSource for it as well. The syntax {Binding} simply means bind to each member or enumeration, in this case ObservableCollection<int>.
Just to reiterate, the template is exactly that - a template for displaying data. It should be reusable, you should be able to bind it to whatever model you want. A rule of thumb - the data binding to actual data should happen on the control, not the template.
Like Igor said, you need specify ItemsSource={Binding DayNumbers} in outer-most ItemsControl, otherwise, it binds to the DataContext, which is CalendarViewModel and it is not IEnumerable.
Once you do that, it will apply <DataTemplate x:Key="PanelOfPanels"> for each item inside DayNumbers. Note that the DataContext of the DataTemplate in each element in DayNumbers, which is of type ObservableCollection<int>. Here you cannot specify ItemsSource="{Binding Path=DayNumbers}" as DayNumbers is not a valid property in ObservableCollection<int>. Instead, since ObservableCollection<int> is already a IEnumerable, it should be fine not specifying ItemsSource since it will by default bind to DataContext.
Finally, it goes to your inner-most <DataTemplate x:Key="ItemsAsButtons">, and you can put button there as what you did.
Hope it clarifies a little bit. Sorry I don't have the environment to test it out and give you the solution.
Debugging WPF bindings is not straightforward. One tip is you can use dummy converter and set breakpoint in the Convert method to see what it binds.
public class DebugConverter1 : IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value;
}
#endregion
}
{Binding Converter={StaticResource debugConverter1}}