Blend switch between 2 visual states (WP7.1) - c#

I think what I am doing is fairly simple, I guess I am just missing something.
I have a StackPanel inside another StackPanel. What I want to do is to hide / show the inner one when the outter one is clicked.
I have 2 states in blend. First makes a StackPanel Collapsed, the other one Expanded.
I added 2 behaviours (GoToStateBaheviour) to the outter StackPanel and assigned their triggers to MouseLeftButtonDown event. In the first behaviour in Conditions I check whether the the inner StackPanel is collapsed, if it is it switches to the state Expanded.
The other behaviour works vice versa - in Conditions I check whether the the inner StackPanel is visible, if it is it switches to the state Collapsed.
Now both of these Behaviours individualy work just fine. But combined, not a chance. I tried to change the Event name of one of them to "ManipulationDelta" and then both started working - but to activate the one I have to try to drag it.
Seems like having 2 behaviours attached to the same event is causing a trouble. What would you recommend?
EDIT
I uploaded the entire solution so you can open it in Blend
http://leteckaposta.cz/800017526
(The project is for WPF as opposed to the one I have for WP7, but that shouldnt matter)
I changed the behaviour to ChangePropertyAction, which should make it easier to read. There are two of them - both react to MouseLeftButtonDown with a Condition for Visibility property. one of them changes it to Visible, the other one to Collapsed.
But only one of them works. I suspect it is always the "upper one" (the one that comes first). Feel free to test it out yourselves
CODE
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
x:Class="WpfApplication1.MainWindow"
x:Name="Window"
Title="MainWindow"
Width="640" Height="480">
<Grid x:Name="LayoutRoot">
<StackPanel Height="168" Width="305" HorizontalAlignment="Left" VerticalAlignment="Top" Background="#FF7C7070">
<TextBlock Height="59" TextWrapping="Wrap" Text="Outter">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseLeftButtonDown">
<i:Interaction.Behaviors>
<ei:ConditionBehavior>
<ei:ConditionalExpression>
<ei:ComparisonCondition LeftOperand="{Binding Visibility, ElementName=stackPanel}" Operator="Equal">
<ei:ComparisonCondition.RightOperand>
<Visibility>Visible</Visibility>
</ei:ComparisonCondition.RightOperand>
</ei:ComparisonCondition>
</ei:ConditionalExpression>
</ei:ConditionBehavior>
</i:Interaction.Behaviors>
<ei:ChangePropertyAction TargetObject="{Binding ElementName=stackPanel}" PropertyName="Visibility">
<ei:ChangePropertyAction.Value>
<Visibility>Collapsed</Visibility>
</ei:ChangePropertyAction.Value>
</ei:ChangePropertyAction>
</i:EventTrigger>
<i:EventTrigger EventName="MouseLeftButtonDown">
<i:Interaction.Behaviors>
<ei:ConditionBehavior>
<ei:ConditionalExpression>
<ei:ComparisonCondition LeftOperand="{Binding Visibility, ElementName=stackPanel}" Operator="NotEqual">
<ei:ComparisonCondition.RightOperand>
<Visibility>Visible</Visibility>
</ei:ComparisonCondition.RightOperand>
</ei:ComparisonCondition>
</ei:ConditionalExpression>
</ei:ConditionBehavior>
</i:Interaction.Behaviors>
<ei:ChangePropertyAction TargetObject="{Binding ElementName=stackPanel}" PropertyName="Visibility">
<ei:ChangePropertyAction.Value>
<Visibility>Visible</Visibility>
</ei:ChangePropertyAction.Value>
</ei:ChangePropertyAction>
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBlock>
<StackPanel x:Name="stackPanel" Height="106" RenderTransformOrigin="0.489,-0.842" Background="#FF708B7C" Visibility="Hidden">
<TextBlock Height="50" Margin="74,0,65,0" TextWrapping="Wrap" Text="Inner"/>
</StackPanel>
</StackPanel>
</Grid>
</Window>

As you've commented in Chris W.'s answer, when you place both triggers in the same event, they compete against each other and cancel each other out. You can verify this by using a MouseLeftButtonUp on one of the triggers.
A simple code behind event can resolve this issue.
XAML
<Grid x:Name="LayoutRoot">
<StackPanel Background="Red" MouseLeftButtonDown="OnMouseLeftButtonDown">
<TextBlock FontSize="22" Text="Outter" />
<StackPanel x:Name="stackPanel"
Height="75"
Background="Yellow">
<TextBlock FontSize="22" Text="Inner" />
</StackPanel>
</StackPanel>
</Grid>
Code Behind
private void OnMouseLeftButtonDown( object sender, MouseButtonEventArgs e )
{
stackPanel.Visibility = stackPanel.Visibility == Visibility.Visible
? Visibility.Collapsed
: Visibility.Visible;
e.Handled = true;
}

What is happening is that your triggers are firing sequentially and not simultaneously. Here is what is happening,
MouseLeftButtonDown event fired
First trigger activates, checks the Visibility, it is Visible so it changes it to Collapsed
Second trigger activates, checks the Visibility it is Collapsed so it changes it to Visible
The net result is that it is stuck in the Visible state. You can verify this by changing the order of the Triggers - the result will be stuck in the Collapsed state.
One way around this that does not require code-behind for each instance is to use a Converter which 'flips' Visible to Collapsed. you can then use a ChangePropertyAction to set a value which is determined via a Binding through this converter, e.g.
<i:Interaction.Triggers>
<i:EventTrigger EventName="Tap">
<ec:ChangePropertyAction
TargetName="stackPanel"
PropertyName="Visibility"
Value="{Binding Visibility,
Converter={StaticResource VisibilityToOpposite},
ElementName=stackPanel}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
The Converter looks like this,
public class VisibilityToOpposite : System.Windows.Data.IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo cultureInfo)
{
Visibility vis = (Visibility)value;
return (vis == Visibility.Collapsed) ? Visibility.Visible : Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo cultureInfo)
{
throw new NotImplementedException();
}
}
The great thing about this method is that the Converter can be reused and also is not limited to two states e.g.
public class StringToNextString : System.Windows.Data.IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo cultureInfo)
{
string s = (string)value;
List<string> allStrings = (parameter as string).Split(new char[] { '|' }).ToList();
// Find the index of the next string along
int i = allStrings.IndexOf(s);
i = (i + 1) % allStrings.Count;
return allStrings[i];
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo cultureInfo)
{
throw new NotImplementedException();
}
}
and then call with a ConverterParameter of pipe delimited strings to cycle through e.g.
Value="{Binding Text, ConverterParameter=one|two|three,
Converter={StaticResource StringToNextString},
ElementName=stackPanel}"

Related

C# WPF math expressions on variables in XAML

I digged out the internet in order to find a solution, and didn't found any clear solution and none of them working.
I'm looking for a specific solution, but if someone here have better solution it will be great..
In general, I'm trying to make controls that based on the Width and Height of the monitor.
- The easy way is put values that matches with my monitor and change them from CodeBehind.
Here's how it going:
- Create two variables in XAML (let's say the type is Double)
- Create third variables also in XAML. His value will be the difference of the
other two variables (in absolute)
- Create 3 control, the width of those controls is the value of the variables
<Window xmlns:sys="clr-namespace:System;assembly=mscorlib >
<sys:Double x:Key="size1">290</sys:Double>
<sys:Double x:Key="size2">450</sys:Double>
<sys:Double x:Key="size3"> Maybe something here? </sys:Double>
<StackPanel>
<Button Name="Button1" Width="{Binding Source={StaticResource size3}}"
Height="50" />
</StackPanel>
</Window>
I'm new in WPF, I do know C# pretty well but XAML is new for me (few months).
Basically I want to know if its possible to use undefined variables, such as 10% of the screen width.
Thanks.
You can use a Converter to achieve that.
public class WidthConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (double)value / 10;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
in Xaml
<StackPanel>
<StackPanel.Resources>
<local:WidthConverter x:Key="WidthConverter" />
</StackPanel.Resources>
<Button Name="Button1"
Width="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}}, Path=ActualWidth, Converter={StaticResource WidthConverter}}"
Height="50" />
</StackPanel>

C# WPF: Listbox with drag to select text

I need to create a WPF ListBox that supports two features:
Content Converter Binding:
The items in the ListBox need to be passed to a converter that converts the items to a text format.
Display items in a way that lets users select and copy text from ListBox items
I need the text of each ListBox item to be selectable. Users want to use their mouse to drag-to-select parts of the elements so they can copy the text to their clipboard.
I implemented [this copy/paste solution][1] but it does not let a user select parts of the ListBox item text, rather it supports copying the entire text.
I'm able to create a ListBox using the converter, but I can not figure out how to put the converted text into a control that lets users select the displayed text. Here is what I have:
<ListBox Name="FinishedTestErrorsListBox"
FontSize="12"
ItemsSource="{Binding Path=SelectedComparisonResult.TestFailItems}">
<ListBox.ItemTemplate>
<DataTemplate>
<ContentControl Content="{Binding Converter={StaticResource testFailItemConverter}}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I've tried adding a TextBox to the DataTemplate as shown below...
<TextBlock Text="{Binding Converter={StaticResource testFailItemConverter}}"/>
... but this creates a runtime error caused by sending the wrong type of object to the converter. I know here I'm not setting up the converter binding properly, though I don't have a good grasp on how I should setup the binding here or why this causes errors.
So, my question is:
What content container can I use to let users select text from the individual ListBox items?
Thank you for any help,
Charlie
EDIT
Here's the converter code...
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
ITestFailItem i = (ITestFailItem)value;
return i.Itemize();
}
EDIT 2
The following runtime error is throw when the ListBox is first initialized:
An unhandled exception of type 'System.Windows.Markup.XamlParseException' occurred in PresentationFramework.dll
Additional information: Provide value on 'System.Windows.Baml2006.TypeConverterMarkupExtension' threw an exception
EDIT 3
The culprit is a line of code I'd omitted from the original snippet as I thought it was irrelevant - I've learned a good lesson along the way!
Extension Question
Why does the following snippet cause an error? How can I achieve the desired affect of making the textbox span the entire containing grid?
<TextBox Width="*"
Text="{Binding Path=., Converter={StaticResource testFailItemConverter}}"/>
Try this. TextBlocks don't support text selection, but TextBoxes do. You just have to make it read-only so the user can't modify the text, and change its border thickness and background so they look like labels:
<ListBox Name="FinishedTestErrorsListBox"
FontSize="12"
ItemsSource="{Binding Path=SelectedComparisonResult.TestFailItems}">
<ListBox.Resources>
<converter:TestFailItemConverter x:Key="testFailItemConverter" />
</ListBox.Resources>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding Path=.,
Converter={StaticResource testFailItemConverter},
Mode=OneWay}"
BorderThickness="0"
Background="Transparent"
IsReadOnly="True"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Have You tried TextBox? You can select text inside textbox. Path have to be changed to Path=.
<TextBox Text="{Binding Path=., Converter={StaticResource testFailItemConverter}}" />
There is not much code to work with, but this code works for me:
xaml:
<Window x:Class="StackOverflowTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:s="clr-namespace:StackOverflowTest"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<s:TestFailItemConverter x:Key="testFailItemConverter" />
</Window.Resources>
<Grid>
<ListBox Name="FinishedTestErrorsListBox"
FontSize="12"
ItemsSource="{Binding Path=SelectedComparisonResult.TestFailItems}">
<ListBox.ItemTemplate>
<DataTemplate>
<!--<ContentControl Content="{Binding Converter={StaticResource testFailItemConverter}}"/>-->
<TextBox Text="{Binding Path=., Converter={StaticResource testFailItemConverter}}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Window>
Model's code:
public class Dummy
{
public ObservableCollection<string> TestFailItems { get; set; }
public Dummy()
{
TestFailItems = new ObservableCollection<string>(new List<string> { "a", "b" });
}
}
public class Model
{
public Dummy SelectedComparisonResult { get; set; }
public Model()
{
SelectedComparisonResult = new Dummy();
}
}
Converter's code:
public class TestFailItemConverter: IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return "aa";
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value;
}
}

Change the foreground color of a TextBlock in the LongListSelector

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.

Avoiding infinite loop in ItemsControl having radiobutton value set by Converters

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

SizeToContent paints an unwanted border

Whenever I try to make a window and I set the SizeToContent to WidthAndHeight, on opening the window correctly sizes to it's contents, but it adds a small border to the right and the bottom. On resizing this disappears, and when using a set height and width this problem also doesn't occur.
This is a sample of what I mean:
You could say this is not a huge problem, though I find it makes my application look unprofessional, especially when I need to present this. Does anybody know why this is happening, or whether there is a workaround? I am coding this project in C#.
XAML Code:
<Window x:Class="FPricing.InputDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="InputDialog" Width="400" Height="300" SizeToContent="WidthAndHeight">
<StackPanel>
<Label x:Name="question">?</Label>
<TextBox x:Name="response"></TextBox>
<Button Content="OK" IsDefault="True" Click="Button_Click" />
</StackPanel>
</Window>
Values are passed on on creation of the class.
However I experience this problem on every window I have ever created, even without custom underlying code.
<Window UseLayoutRounding="True" /> works for me.
Using this tool (it's good, btw) I found that the Border control of the Window (it's immediate child) doesn't fill the whole window, leaving that "border", which is actually the background of the Window control.
I've found a workaround. Width and Height of the Border are NaN. If you set those to an integer value, the "border" disappears.
Let's use the values of ActualWidth and ActualHeight, but rounded to an integer.
Define the converter:
C#
[ValueConversion(typeof(double), typeof(double))]
public class RoundConverter : IValueConverter {
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
return Math.Ceiling((double)value);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
return value;
}
}
XAML (remember to include your namespace, in this case "c")
<c:RoundConverter x:Key="RoundConverter"/>
Then create a style binding the size to the actual size using the converter. It's important to use a Key, so it won't apply to every Border (most controls use it):
<Style TargetType="{x:Type Border}" x:Key="WindowBorder">
<Setter Property="Width" Value="{Binding RelativeSource={RelativeSource Self}, Path=ActualWidth, Converter={StaticResource RoundConverter}}"/>
<Setter Property="Height" Value="{Binding RelativeSource={RelativeSource Self}, Path=ActualHeight, Converter={StaticResource RoundConverter}}"/>
</Style>
Finally, apply this style to the first child of the window (the Border control):
private void Window_Loaded(object sender, RoutedEventArgs e) {
GetVisualChild(0).SetValue(StyleProperty, Application.Current.Resources["WindowBorder"]);
}
If someone can do this in a simpler way, please share too.
managed to solve it, by combining Gabriel and smg answers.
Upon loading the window, get the border in question, and set its LayoutRounding to true.
this.Loaded += (sender, args) =>
{
var border = this.GetVisualChild(0) as Border;
if (border != null)
border.UseLayoutRounding = true;
};
Okay, heres a related answer in which you can refer to a great answer.
Automatic resizing when border content has changed
So basically you want to add something like this, but put it to the values that you want:
<Border x:Name="border"
BorderBrush="Cornsilk"
BorderThickness="1">
<Ellipse Width="40"
Height="20"
Fill="AliceBlue"
Stroke="Black" />
</Border>

Categories