I have this code in WPF
Every new form gets added by clicking on "Add New" to the Itemscontrol.
Event is a CSLA call.
<Menu Grid.Row="0">
<MenuItem Header="Add New"
csla:InvokeMethod.MethodName="AddNew"
csla:InvokeMethod.TriggerEvent="Click" />
</Menu></ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate >
<DataTemplate.Resources>
<FrameworkElement x:Key="ReqProxyElement" DataContext="{Binding}" />
</DataTemplate.Resources>
<Grid>
<ContentControl Visibility="Collapsed" Content="{StaticResource ReqProxyElement}" />
<Grid>
<Grid.DataContext>
<formviewmodels:ReqViewModel Model="{Binding Source={StaticResource ReqProxyElement}, Path=DataContext}" />
</Grid.DataContext>
<formviews:ReqView />
</Grid>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Now inside the form ReqView, I have converter call for radio button.
<Label Grid.Row="10" Grid.Column="0" Content="required" />
<StackPanel Orientation="Horizontal" Grid.Row="10" Grid.Column="1" >
<!--<RadioButton Content="Yes" GroupName="Required" IsChecked="{Binding Model.Required, Converter={StaticResource NullableBooleanToFalseConverter}}"></RadioButton>
<RadioButton Content="No" GroupName="Required" IsChecked="{Binding Model.Required, Converter={StaticResource ReverseBoolean}}"></RadioButton>-->
<RadioButton Content="Yes" GroupName="GRequired" ></RadioButton>
<RadioButton Content="No" GroupName="GRequired" ></RadioButton>
</StackPanel>
In this scenario when I click on add New , the ItemsControl as is the nature of the beast tries to bind back to the Form and goes into an infinite loop in the converter call.
The converter code is given below.
public class ReverseBooleanConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is bool)
{
return (!((bool)value));
}
return false;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is bool)
{
return (!((bool)value));
}
return value;
}
}
public class NullableBooleanToFalseConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value == null)
{
return false;
}
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return value;
}
}
Can any one come up with a solution where the convereter don't kick the code into infinite loop.
What happens is when a Add New is clicked, if there is already a form in the Itemscontrol, it tries to bind back to the form again before creating a new empty form.
The binding back sets the radio button true say if true is selected but then setting it to trus starts a tennis match between the two converters one converts it the other converts it back and the model says No way the value is true and so on it goes until application hits stackoverflow...
Interesting situation I have hit into with WPF and MVVM pattern.I am looking for a solution without breaking the MVVM paradigm. If converters can be done away with that will work too.
The backend calls are CSLA reistered properties.
Thanks
Dhiren
You can bind only the Yes radio. Since it's IsChecked property is bound to your bool, the bool would turn to false when you check the other radio. You don't even need the ReverseConverter here.
UPDATE:
Only bind the Yes radio to your field. RadioButton are mutually exclusive when they are in the same group (which they are). When you select one, you change the other. If you select the No radio, yes would be unchecked, and so Required would be false.
As for the initial values, if you set No to false, it would be selected. And since Yes is bound to Required, it'll save your value. Do something like this:
<RadioButton Content="Yes" GroupName="GRequired" IsChecked="{Binding Required}"/>
<RadioButton Content="No" GroupName="GRequired" IsChecked="False"/>
UPDATE II:
The binding mechanism in WPF ties two values together, so when one changes the other does too, and it can work both ways. RadioButton.IsCheck is just a bool property, so when it's value change it changes the value it's bound to (in this case Required). When you check No, you also uncheck Yes, it's IsChecked property changes. and that changes Required.
For more on Data binding see: http://msdn.microsoft.com/en-us/library/ms752347.aspx.
So, you see, you don't need the ReverseConverter, because you don't need to bind No to anything. As a matter of fact, you don't even need NullBooleanConverter, since WPF already knows how to convert bool? to bool.
Regards,
Yoni
Related
I have the following property in my ViewModel
public IEquipment Equipment
{
get
{
return equipment;
}
set
{
if (equipment != value)
{
equipment = value;
InvokePropertyChanged("Equipment");
}
}
}
This item itself has a bool property, which is bound to an Ellipse in my View, which I want to use as a indicator item:
<Ellipse Width="10" Height="10" Fill="{Binding Equipment.IsAvailable, Converter={StaticResource BoolToColorConverter}, FallbackValue=DarkGray}" Margin="1"/>
The BoolToColorConverter simply converts the color to either green (true) or red (false). During runtime Equipment can be an instance of one of two class types which inherit from IEquipment. Only one of them has the IsAvailable property. In practice this works fine, I get eighter my red or green color...or a gray one, in case the other type of equipment is active.
Problem is, that each time the GUI updates, the following warning gets output:
System.Windows.Data Warning: 40 : BindingExpression path error: 'IsAvailable' property not found on 'object'
How can I avoid this issue? Basically I want to bind to this property only if it is of the correct type.
I can think of two solutions, which I'm not particularly fond of:
Simply add the IsAvailable property to the other type and set it to null (BoolToColorConverter can handle null values and returns dark grey): This might be ok for a simple bool, but in my actual case there are other items, which are quite class specific.
Do the databinding in the code-behind: This might work. Using an event like Loaded on startup to set the binding manually at runtime based on the type. However, this might be troublesome for debugging later, because all other Bindings in the project happen directly in the xaml file. Additionally, Equipment might change during the lifetime of the ViewModel, so I would have to somehow track it.
Xaml doesn't bind to interfaces, it binds to concrete types.
If your types have different properties, then you need different xaml to bind them.
Use DataTemplates to specify different xaml for displaying each type.
If the properties on your derivatives of IEquipment (here Equipment and OtherEquipment as examples) differ a lot and do not share a common interface, they most likely differ in their appearance. In this case you would need different DataTemplates for each type. This is an example for a ContentControl, but it works the same fot ItemsContols with implicit data templates (no x:Key, but a DataType) that are applied automatically.
<ContentControl Content="{Binding Equipment}">
<ContentControl.Resources>
<DataTemplate DataType="{x:Type local:Equipment}">
<Ellipse Width="10" Height="10" Fill="{Binding IsAvailable, Converter={StaticResource BoolToColorConverter}, FallbackValue=DarkGray}" Margin="1"/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:OtherEquipment}">
<Ellipse Width="10" Height="10" Fill="DarkGray" Margin="1"/>
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
A workaround for your specific issue could be writing a custom, specialized value converter.
public class EquipmentAvailabilityToColorConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is Equipment equipment)
return equipment.IsAvailable ? Brushes.Green : Brushes.Red;
return (Brush)parameter;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new InvalidOperationException();
}
}
<Ellipse Width="10" Height="10" Fill="{Binding Equipment, Converter={StaticResource EquipmentAvailabilityToColorConverter}, ConverterParameter={x:Static Brushes.DarkGray}}" Margin="1"/>
I have a ComboBox that grabs its data from a List of strings.
Styles.Add("bold");
Styles.Add("italic");
Styles.Add("underline");
Styles.Add("");
When shown in the ComboBox, I want it to actually display: { "bold", "italic", "underline", "[Discard style]" }, respectively. It's important that I'm able to change that empty string value to display "[Discard style]" but also retain the empty string value when the SelectedChanged event fires.
Is there some way to do this (without changing the underlying data structure to support a KeyValuePair)?
This is what I originally started with:
<ComboBox ItemsSource="{Binding Styles, Mode=OneWay}" SelectedItem="{Binding SelectedStyle}" SelectionChanged="StyleComboBox_SelectionChanged" />
Since then, I've tried adding and doing many variations/combinations of
<ComboBox ItemsSource="{Binding Style, Mode=OneWay}" SelectedItem="{Binding SelectedStyle}" DisplayMemberPath="{Binding SelectedStyle, Converter={StaticResource StyleConverter}}" SelectionChanged="StyleComboBox_SelectionChanged" Height="22" Width="175" />
with SelectedValuePath={Binding SelectedStyle}, where StyleConverter is as follows:
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
string selectedStyle = value as string;
if (string.IsNullOrEmpty(selectedStyle))
return "[Discard style]";
return value;
}
I've mostly encountered the scenario where the display value is correct, but the underlying value is not - it's somehow always changed to "[Discard style]" as well. Do I maybe need two converters somehow? One just for displaying in the ComboBox and another just for selecting the item?
Another important note is that "[Discard style]" will be translated in the UI based on the operating system. So I can't simply just compare the string to see if it matches and turn it back into an empty string.
There are actually quite a few different ways of achieving this, but for something this simple I'd probably just use a DataTemplate with a DataTrigger to change the text when it's an empty string:
xmlns:sys="clr-namespace:System;assembly=mscorlib"
<ComboBox ItemsSource="{Binding Styles, Mode=OneWay}" SelectedItem="{Binding SelectedStyle}">
<ComboBox.Resources>
<DataTemplate DataType="{x:Type sys:String}">
<TextBlock>
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Text" Value="{Binding}" />
<Style.Triggers>
<DataTrigger Binding="{Binding}" Value="">
<Setter Property="Text" Value="[Discard style]" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</DataTemplate>
</ComboBox.Resources>
</ComboBox>
You will, however, need to use a converter if you want to handle null in your string list. In that case ignore the code above and apply the converter to the ItemsSource binding:
<Window.Resources>
<conv:StringConverter x:Key="StringConverter" />
</Window.Resources>
<StackPanel Orientation="Vertical">
<ComboBox ItemsSource="{Binding Styles, Mode=OneWay, Converter={StaticResource StringConverter}}" SelectedItem="{Binding SelectedStyle}" />
</StackPanel>
And then your converter would just look something like this:
public class StringConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (value as IEnumerable<string>).Select(s => String.IsNullOrEmpty(s) ? "[Discard style]" : s);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
UPDATE: Oh ok, I see what you're trying to do. In that case yes, you need to use two converters....one to filter the list of strings that the view model is passing to the view, and then a second to change the value being propagated back again.
Before I go any further I should point out that what you're trying to do is a really, really bad idea. The whole point of MVVM is that logic is done in the view model. What you're effectively trying to do is implement logic in the view layer, and that's completely against the whole MVVM/data-binding paradigm. The whole purpose of the view model is to prepare data in a format that the view can readily consume with as few changes as possible; the minute you find yourself putting logic in the view (including converters, which are also part of that layer) it's a very strong sign that your view model is not doing its job properly.
To answer your question though, you would need a converter in your SelectedStyleBinding in addition to the one I posted above. You would now also be forced to make that binding one-way-to-source, which means you lose the ability to programatically control the currently selected item:
<ComboBox ItemsSource="{Binding Styles, Mode=OneWay, Converter={StaticResource StringConverter}}" SelectedItem="{Binding SelectedStyle, Converter={StaticResource SingleStringConverter}, Mode=OneWayToSource}" />
And the second converter:
public class SingleStringConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return Binding.DoNothing;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return (value?.ToString() == "[Discard style]") ? "" : value?.ToString();
}
}
If you need to retain programatic item selection then your only other option is to bind to SelectedIndex instead of SelectedItem, your view model would then have to be responsible for looking the string up in the source list.
But again, all of this is very bad code. The correct thing to do here is to modify your source list to be a custom view model class or something else your view can more readily work with.
We're in the process of updating our gallery WPF application which contains our custom styled controls. The design is to have a toggle to show the XAML behind those custom controls, for easy reference and a guide for new colleagues.
The way I've currently implemented this is by creating two .xaml files, one containing just the controls, one with the controls and a textblock with the XAML coded used to implement those controls.
This is not something that's easily maintainable, since the quotes, >,< and other characters are not escaped in XAML strings. For reference this is what I have now in one of the 'Show code' views:
<TextBlock Visibility="Collapsed" Margin="5" Text="<controls:AutoCompleteTagBox
Name="AutoCompleteTagBoxWithStrings"
Margin="5"
ItemsSource="{Binding Names}"
FilterMode="Contains" />
<ListBox
ItemsSource="{Binding ElementName=AutoCompleteTagBoxWithStrings, Path=SelectedItems}"
Grid.Column="1"
BorderBrush="{StaticResource Blue}" BorderThickness="1" />"/>
As you can see, it doesn't look nice and once you update one of the controls you now have three places you need to change the XAML.
The next step is just to bind the TextBlock visibility and toggle it from 'Collapsed' to 'Visible'. But I want to know if there is a way to show the XAML in a textblock without having to hand write the string.
Thanks in advance for your advice!
Following from XAMIMAX's comment you could use an easy converter to save the xaml to a string using XamlWriter and strip the xmlns namespaces for brevity.
public class XamlConverter : IValueConverter
{
public readonly Regex xmlnsRegex = new Regex("xmlns=\".+\"");
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var source = value as FrameworkElement;
if (source != null)
{
//Save xaml and strip xmlns namespaces
var xaml = xmlnsRegex.Replace(System.Windows.Markup.XamlWriter.Save(source), "");
return xaml;
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Example usage
<StackPanel>
<StackPanel.Resources>
<converters:XamlConverter x:Key="Converter_Xaml"/>
</StackPanel.Resources>
<Button x:Name="SourceButton" Content="Click Me" Margin="10"/>
<TextBlock Text="{Binding ElementName=SourceButton, Converter={StaticResource Converter_Xaml}}" TextWrapping="Wrap"/>
</StackPanel>
I have a longlistselector for my windows phone 8 app:
<phone:LongListSelector x:Name="AppMenuList" Background="Transparent"
ItemTemplate="{StaticResource AppMenuListTemplate}"
IsGroupingEnabled="true" HideEmptyGroups="true"
LayoutMode="List" SelectionChanged="OnMenuItemTapped"
Margin="5,50,0,0"/>
With the following DataTemplate:
<phone:PhoneApplicationPage.Resources>
<DataTemplate x:Key="AppMenuListTemplate">
<Grid>
<StackPanel Orientation="Horizontal" Margin="0,5,0,0" Height="80" Width="800" Tap="vTapFeedback">
<TextBlock HorizontalAlignment="Left" Margin="0,20,0,20" Height="50"
Width="800" TextWrapping="NoWrap"
Text="{Binding MenuItemName}" VerticalAlignment="Center"
FontSize="32" Foreground="#115445" />
</StackPanel>
</Grid>
</DataTemplate>
</phone:PhoneApplicationPage.Resources>
From my C# code, I am setting the ItemsSource property for the longlistselector to display the list of items to the user. However all the items are of same color as specified in the TextBlock property Foreground.
I have a requirement in which I want the user to be able to tap every item of the list and perform some operation except the one. I want that it should be shown as disabled to the user by using a Gray color for it.
I am not able to accomplish this. Can anyone suggest how I can do this ?
There are three solutions that come to my mind:
you can use VisualTreeHelper to find your textbox and then change its Foreground Color
I've bound Foreground color to a property of Item Class, then when I change this property, Foreground changes automatically. I assume than you were able to bind your Text, then there should be no problem with binding Foreground. One thing you will probably need is a Converter.
you can define VisualStates in Style of your TextBlock.
EDIT - code sample after request
I've definded my Converter like this:
namespace myApp.Converters
{
public class BoolToBrush : IValueConverter
{
private Brush FalseValue = (Application.Current.Resources["TransparentBrush"] as Brush);
private Brush TrueValue = (Application.Current.Resources["PhoneAccentBrush"] as Brush);
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value == null)
return FalseValue;
else
return (bool)value ? TrueValue : FalseValue;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value != null ? value.Equals(TrueValue) : false;
}
}
In my Item class I've a property Selected (bool in this case) to which Foreground (or Background) is bound. I use it in XAML (item or control) like this:
<UserControl.Resources xmlns:local="clr-namespace:myApp.Converters">
<local:BoolToBrush x:Key="boolToBrush"/>
</UserControl.Resources>
<Grid Name="myElement" Background="{Binding Path=Selected, Converter={StaticResource boolToBrush}}">
Of course you can define more coplex convertes - if you need more Brushes and so on.
On the other hand I would also consider using VisualStates.
Hope this helps a little.
I have a ListBox on a WPF User Control that is defined as
<ListBox Grid.Row="1" ScrollViewer.CanContentScroll="False" Background="#00000000" BorderThickness="0" ItemsSource="{Binding BuyItNowOptions}"
ItemTemplate="{DynamicResource BuyItNowDataTemplate}" IsSynchronizedWithCurrentItem="True"
Style="{DynamicResource InheritEmptyListStyle}" SelectedItem="{Binding SelectedResearch}" ItemContainerStyle="{DynamicResource ListBoxItemStyle}"/>
BuyItNowOptions is a public property on the ViewModel that is of type ObservableCollection
In the BuyItNowDataTemplate I have a label that needs to have some logic performed before displaying a price.
<Label Padding="1" HorizontalContentAlignment="Stretch" Grid.Column="2" Grid.Row="2" Margin="1">
<TextBlock Text="{Binding ExchangePrice, StringFormat=C}"
Visibility="{Binding ReturnRequired, Converter={StaticResource BooleanToVisibilityConverter}}"/>
</Label>
The binding here indicates that it will use the ExchangePrice property of the instance of AutoResearchProxy that it is on like BuyItNowOptions[CurrentIndex].ExchangePrice.
What I would like to know is it possible to create the binding in such a way that it references the whole instance of the AutoResearchProxy so that I can pass it to a converter and manipulate several properties of the AutoResearchProxy and return a calculated price?
I would envision my converter looking like this.
public class PriceConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value is AutoResearchProxy)
{
var research = value as AutoResearchProxy;
//Some logic to figure out actual price
}
else
return String.Empty;
}
Hopefully this makes sense.
You can pass the whole datacontext-object to a Binding by not specifying a Path or by setting it to ., that however will result in the binding not updating if any of the relevant properties of that object change.
I would recommend you use a MultiBinding instead, that way you can target the necessary properties and the binding will update if any of those change. (For usage examples see the respective section on MSDN)
MyProperty="{Binding Converter={StaticResource ObjectToDerivedValueConverter}
That should do it.