I have a XAML file in my Xamarin project that displays different views depending on the state of 2 picker views. The Picker View is a custom view that lets you display an Enumerator as a picker. The important part is, that the SelectedItem does fire the PropertyChanged notification.
So in my Xaml I define my style like this:
<Style x:Key="SinglePressureRelativeHumidity" TargetType="ContentView">
<Style.Triggers>
<MultiTrigger TargetType="ContentView">
<MultiTrigger.Conditions>
<BindingCondition Binding="{Binding Source={x:Reference Mode}, Path=SelectedItem}"
Value="{x:Static enums:HumidityCalculatorMode.SinglePressure}" />
<BindingCondition Binding="{Binding Source={x:Reference KnownValue}, Path=SelectedItem}"
Value="{x:Static enums:HumidityCalculatorKnownValue.RelativeHumidity}" />
</MultiTrigger.Conditions>
<MultiTrigger.Setters>
<Setter Property="IsVisible" Value="true" />
</MultiTrigger.Setters>
</MultiTrigger>
</Style.Triggers>
</Style>
This is placed inside the local resource dictionary. The References Mode and KnownValue refer to the custom picker views, which are correctly defined in the same xaml file.
I later have a StackLayout with multiple ContentPages each looking similar to the following:
<ContentView Style="{StaticResource SinglePressureRelativeHumidity}"
IsVisible="False">
<StackLayout>
<controls:TemperatureEntry Title="Temperature"
Temperature="{Binding HumidityCalculator.InputTemperature, Mode=TwoWay}"/>
<controls:PressureEntry Title="Test Pressure"
Pressure="{Binding HumidityCalculator.InputPressure, Mode=TwoWay}" />
</StackLayout>
</ContentView>
Where each ContentPage has its own Style with different Conditions.
Now to the problem, when I change the value of any of the picker the ContentPages get enabled or disabled as you would expect, the one where the style's MultiTrigger's Conditions are met gets set to visible all other are set to invisible.
However, the problem is that when loading the view all are set to invisible. So it is as if the trigger only checks when there are changes made by the user. I have tested various things.
First I tried setting the value of both pickers to the wanted default value after the InitializeComponent method without success. I made sure that the Property SelectedItem does fire the PropertyChanged notification with the correct name.
Second I tried inverting the isVisible property of the ContentViews to true but then all were visible which also wasn't what i wanted.
So how can I trigger the MultiTrigger with my default values?
I was able to fix this by binding the BindingCondition directly to the Model which the Pickers set their values to.
I am not sure why this fixed the issue.
Related
I created a WPF (.Net 4) UserControl containing some ComboBoxes and a TextBox. In XAML, some ValidationRules are bound to the TextBox. If the TextBox contains invalid data, a red frame is shown, and the tooltip is set to the error description. Works well.
Next, I placed two instances of that UserControl on a form, and added a button. In XAML, the button is connected to a RelayCommand of the ViewModel. Now I want the button to be enabled only when both of the UserControls contain valid data only.
Not a problem, I thought, let me use a strategy which works elsewhere. I added a trigger:
<Button Content="_OK" ... Command="{Binding Path=OKCommand}">
<Button.Style>
<Style TargetType="{x:Type Button}">
<Setter Property="IsEnabled" Value="false" />
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding ElementName=cascadingComboFrom, Path=(Validation.HasError)}" Value="false" />
<Condition Binding="{Binding ElementName=cascadingComboTo, Path=(Validation.HasError)}" Value="false" />
</MultiDataTrigger.Conditions>
<Setter Property="IsEnabled" Value="true" />
</MultiDataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
But there is a catch: Validation.HasError is always false for the UserControls - the Validation failed for an element inside the UserControl, not for the UserControl proper.
I know that I can register the Click event of the button, check the validity there using the method shown in Detecting WPF Validation Errors, and prevent the execution of the RelayCommand by setting the Handled property of the RoutedEventArgs to true. But that looks more like WTF than WPF.
What do you suggest? How can I retrieve the Validation Errors of the UserControl's children? Or how can I get them in the RelayCommand's CanExecute method? Or some other tricks?
You can set a property on the command binding called ValidatesOnDataErrors.
Implementation would look something like this:
<Button Content="_OK" Command="{Binding, Path=OKCommand, ValidatesOnDataErrors=True}"/>
You can read more about it here.
The Button.IsEnabled property is already hard wired to the CanExecute method of your RelayCommand, so all you need to do is to set that return value to false when the form fields are invalid:
private bool CanExecute(object commandParameter)
{
return areFormFieldsValid;
}
Now, how you set the bool areFormFieldsValid variable to true or false is up to you... there are several ways of doing that. Personally, I prefer to use the IDataErrorInfo interface, which has a handy Error property that you can check. There are many online tutorial on how to implement this, so I won't repeat that here... however, the end result is something like this:
private bool CanExecute(object commandParameter)
{
return string.IsNullOrEmpty(yourDataObject.Error);
}
I'm a long time WPF designer yet new to windows app development. I'm trying to bind a collection of objects onto a grid yet keep getting the error Unknown attachable member '(Grid.Row)' on element 'FrameworkElement' and Unknown attachable member '(Grid.Column)' on element 'FrameworkElement'.
Can someone please explain to me the how to set the various Grid attached properties via style?
<ItemsControl ItemsSource="{Binding}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid>
<!-- Column and row definitions omitted for brevity -->
</Grid>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="FrameworkElement">
<Setter Property="(Grid.Row)" Value="{Binding Row}" />
<Setter Property="(Grid.Column)" Value="{Binding Column}" />
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
Don't use a PropertyPath. All you need is a qualified Owner.Property string.
<Setter Property="Grid.Row" Value="{Binding Row}" />
Taken from PropertyPath XAML Syntax
Some style and template properties such as Setter.Property take a
qualified property name that superficially resembles a PropertyPath.
But this is not a true PropertyPath; instead it is a qualified
owner.property string format usage that is enabled by the WPF XAML
processor in combination with the type converter for
DependencyProperty.
It turns out that there are actually 3 problems with the code I posted above.
As #LPL correctly identified Setter.Value takes a qualified property name where a PropertyPath was being used. The fix here is to drop the parentheses: <Setter Property="Grid.Row" ... /> and <Setter Property="Grid.Column" ... />.
The second issue is with the style target type. It turns out that the metro Grid attached properties can't be applied to FrameworkElement's. The solution here is to update the target type with something more specific: <Style TargetType="ContentPresenter" />.
Finally as with Silverlight, the value property of metro setters don't support bindings. Consequently even after fixing the previous two errors, the setter is actually trying to set the grid attached properties to an instance of type Binding. While not as straight forward, all the details of a solution may be found here. In summary you can use the setter to set a custom attached property, which will in turn set up any desired binding.
I'm starting a new project in WPF and am now looking into using Prism. For now I'm simply trying to set up the navigation of the application using Prism. Unfortunately, my lack of experience with the framework makes it a bit difficult to get started.
To be more precise about my first challenge I have an application with a "navigation/menu" region and a "main" region.
In "navigation/menu" region, I have several checkboxes, in this case we have four of them, which represents a sequential navigation. I.E. we've selected View 2 and View 4.
So, when the user click Start, in "main" region must appear each view selected in that order. Check the below image, View 2 is first. Then when the user press next, must show View 4.
I mean on a more structural level..
if I could only get through the first steps..
Prism support TabControl Region Adapter, navigation can be done using standard requestNavigation method.
You need add all your tab content using Region.Add method to the region in your module's init phase.
view:
<TabControl prism:RegionManger.RegionName="tabRegion" />
C# code:
IRegionManager manager;
manager.Regions["tabRegion"].Views.Add(Container.Resolve(typeof(YourViewType)));
In your viewModel, you should write you navigation command:
public void NextView() {
regionManager.RequestNavigation("tabRegion", new Uri("YourViewType", UriKind.Relative));
}
bind to your "next" button:
<Button Command="{Binding NextViewCommand}" />
If you want to control whether user can navigate to next page, you can implement INavigationAware interface.
If you don't want lost data between navigation, you can make your view model has ContainerMangedLifeCycle or implement IsNavigationTarget method to return true.
Sorry for untested code sample, but you should get the point.
Create a class named ViewVM with a property IsSelected. Must implement INotifyPropertyChanged.
Add an ObservableCollection<View> named Views to your datacontext. Populate it with new instances of ViewVM.
Put an ItemsControl in your Window, with ItemsSource set to Views. The DataTemplate for the ItemsControl items should contain a CheckBox (with IsChecked bound to IsSelected) and a Label.
Add a TabControl to your Window, with ItemSource set to Views. Add a Style for TabItem such that TabItems are only visible if IsSelected is true.
Following the above steps will give you a window containing a list of views with checkboxes, as you requested, and a TabControl displaying only the selected views. Below is the XAML (I have tested this):
<StackPanel>
<ItemsControl ItemsSource="{Binding Path=Views}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding Path=IsSelected}"></CheckBox>
<TextBlock Text="{Binding Path=Title}"></TextBlock>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<TabControl ItemsSource="{Binding Path=Views}">
<TabControl.ItemTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="{Binding Path=Title}"></TextBlock>
</Grid>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.Resources>
<Style TargetType="TabItem">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsSelected}" Value="False">
<Setter Property="Visibility" Value="Collapsed"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</TabControl.Resources>
</TabControl>
</StackPanel>
This addresses the structural/design aspect and should give you a good start to creating your solution - you'll also need to create a custom control to use instead of the TabControl. Instead of having tabs, your custom control should contain Next and Previous buttons to navigate between views.
WPF application, .NET 3.5.
I have a ListView control, to which I bind a collection of business classes.
I'd like to bind the ForeColor of items to a bool property of the class (say: MyClass.Active), so that the items are displayed in black when active, in light gray when disabled.
I want listview items to change their color on-the-fly, I mean when I'd change Active value of one of the instances in my databound collection, its respective listview item would change its color automatically.
What do I need to achieve this? I've found numerous tutorials on WPF databinding on the net, some questions on StackOverflow, but not exactly the same thing, and I don't want to start combining my solution out of everything I can put my hands on, by trial and error.
I know about INotifyPropertyChanged (for my business class), IValueConverter (but should I need it for a bool??), DataTrigger etc.
But which pieces of the puzzle do I really need, what is the simpliest way to achieve my goal?
I would just do it in a style, and apply that style on your List items. Providing that your object implements INotifyPropertyChanged and the property changed event gets raised when IsActive changes, this will change the foreground to Gray if IsActive = False
<Style x:Key="DisableInactiveTextStyle" TargetType="{x:Type TextBlock}">
<Setter Property="Foreground" Value="Black" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsActive}" Value="False">
<Setter Property="Foreground" Value="LightGray" />
</DataTrigger>
</Style.Trigger>
</Style>
<TextBlock Style="{StaticResource DisableInactiveTextStyle}" ... />
I would suggest creating an IValueConverter that takes a bool and returns a color. You can then bind the ForeColor to the MyClass.Active property and use the created converter.
i've got a silverlight (v2) datagrid where some items are section headers and as such must appear with a different background colour.
i'm trying to do this with the following xaml:
<dg:DataGrid.RowStyle>
<Style TargetType="dg:DataGridRow">
<Setter Property="Background" Value="{Binding Path=Background, Mode=OneTime}" />
</Style>
</dg:DataGrid.RowStyle>
i expect it to bind the Background property of the datagrid row viewmodel to each row's Background property, instead i get a lovely unknown xaml parsing error:
{System.Windows.Markup.XamlParseException: AG_E_RUNTIME_MANAGED_UNKNOWN_ERROR [Line: 16 Position: 57]
at System.Windows.Application.LoadComponent(Object component, Uri resourceLocator)
at Etana.Survey.Silverlight.UserInterface.Views.MaximumProbableLossPage.InitializeComponent()
at Etana.Survey.Silverlight.UserInterface.Views.MaximumProbableLossPage..ctor()}
if i try to explicitly specify "Red" and not try and bind the style, then it works, so I wonder if silverlight would allow me to bind a style like that or if there's some other trick to it.
(the xaml is based on a wpf implementation of this which works fine)
any input would be much appreciated
Change your binding to TemplateBinding. eg
<dg:DataGrid.RowStyle>
<Style TargetType="dg:DataGridRow">
<Setter Property="Background" Value="{TemplateBinding Background, Mode=OneTime}" />
</Style>
</dg:DataGrid.RowStyle>
Silverlight as of version number 4 doesn't support bindings in a Setter Value. There is a workaround implemented as an attached property:
SetterValueBindingHelper