WP7 discards MouseLeftButtonUp if didn't receive ButtonDown - windows-phone-7

I'm trying to get an element to fire the MouseUp event when the user clicks/taps outside of it, drags into it, then lets go. This type of functionality works in Silverlight, but not WP7. I can't figure out how to get it to work in WP7.
I created a simple app that demonstrates this. In a brand new WP7 app I added this to the content panel:
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid x:Name="g1" MouseLeftButtonUp="Grid_MouseLeftButtonUp" Background="Green" />
<Grid x:Name="g2" Grid.Row="1" MouseLeftButtonUp="Grid_MouseLeftButtonUp" Background="Blue" />
</Grid>
Then the Grid_MouseLeftButtonUp handler in the codebehind:
private void Grid_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
System.Diagnostics.Debug.WriteLine("MouseUp in " + (sender as Grid).Name);
(sender as Grid).Background = new SolidColorBrush(Colors.Red);
}
Run this app and notice the MouseUp event fires fine if you release the button in the same cell you pressed down, however it doesn't fire if you drag from one cell to the other. How can I make the MouseUp event fire?
P.S. I also posted this on the app-hub forms, but no response yet: http://forums.create.msdn.com/forums/p/98004/584400.aspx

My solution is a bit like ShawnFeatherly's, but without TouchFrame.
Basically, as he says, if you call MouseCapture from the grid where the MouseDown event occured, the MouseUp will be triggered on the same grid. So we know how to be notified when MouseUp occurs, the only problem left is how to know in which grid the MouseUp actually occured.
For this, we're going to use the VisualTreeHelper.FindElementsInHostCoordinates method, as it returns all the elements at a specified coordinate.
So, first add a MouseLeftButtonDown event handler to each of your grids:
private void Grid_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
((Grid)sender).CaptureMouse();
}
Now in the MouseLeftButtonUp event handler of each of your grids, first release the mouse capture, then retrieve the Grid in which the MouseUp occured:
private void Grid_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
var grid = (Grid)sender;
grid.ReleaseMouseCapture();
var mouseUpGrid = VisualTreeHelper.FindElementsInHostCoordinates(e.GetPosition(this), this.ContentPanel)
.OfType<Grid>()
.FirstOrDefault();
if (mouseUpGrid != null)
{
Debug.WriteLine("MouseUp in " + mouseUpGrid.Name);
mouseUpGrid.Background = new SolidColorBrush(Colors.Red);
}
}
Note that a problem may occurs depending on your visual tree: if you have multiple grids and wants to detect the MouseUp only on some, you need a way to identify them. For this, I suggest to use the Tag property. Tag is an all-purpose field available on each control, that you can use however you need. It's especially useful for identification purposes.
Start by adding it to the grids that interest you:
<Grid x:Name="ContentPanel"
Grid.Row="1"
Margin="12,0,12,0"
MouseLeftButtonUp="ContentPanel_MouseLeftButtonUp">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid x:Name="g1"
Background="Green"
MouseLeftButtonDown="Grid_MouseLeftButtonDown"
MouseLeftButtonUp="Grid_MouseLeftButtonUp"
Tag="dragdrop" />
<Grid x:Name="g2"
Grid.Row="1"
Background="Blue"
MouseLeftButtonDown="Grid_MouseLeftButtonDown"
MouseLeftButtonUp="Grid_MouseLeftButtonUp"
Tag="dragdrop" />
</Grid>
Then use exactly the same logic in code-behind, but this time add a filter when browsing the visual tree:
private void Grid_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
var grid = (Grid)sender;
grid.ReleaseMouseCapture();
var mouseUpGrid = VisualTreeHelper.FindElementsInHostCoordinates(e.GetPosition(this), this.ContentPanel)
.OfType<Grid>()
.FirstOrDefault(element => element.Tag is string && (string)element.Tag == "dragdrop");
if (mouseUpGrid != null)
{
Debug.WriteLine("MouseUp in " + mouseUpGrid.Name);
mouseUpGrid.Background = new SolidColorBrush(Colors.Red);
}
}
And you're done! This code should be able to handle complex scenarios like:
<Grid x:Name="ContentPanel"
Grid.Row="1"
Margin="12,0,12,0"
MouseLeftButtonUp="ContentPanel_MouseLeftButtonUp">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid x:Name="g1"
Background="Green"
MouseLeftButtonDown="Grid_MouseLeftButtonDown"
MouseLeftButtonUp="Grid_MouseLeftButtonUp"
Tag="dragdrop" />
<Grid x:Name="DummyGrid" Grid.Row="1">
<Grid x:Name="g2"
Background="Blue"
MouseLeftButtonDown="Grid_MouseLeftButtonDown"
MouseLeftButtonUp="Grid_MouseLeftButtonUp"
Tag="dragdrop" />
</Grid>
</Grid>

One way to work around this is listening to the TouchFrame for a TouchAction.Up. You'll have to calculate the UIElement the ButtonUp cooresponds to using the TouchPoints' Position property as described here: http://forums.create.msdn.com/forums/p/98004/584465.aspx#584465
Another way is to capture the mouse in the ButtonDown UIElement. This will cause the ButtonUp to correctly fire, however the sender will be the original UIElement that caused the ButtonDown. You can track the elements the mouse moves through using MouseEnter and MouseLeave. The necessity for mouse capture is briefly touched on here: http://forums.create.msdn.com/forums/p/70785/431882.aspx

Last time I checked, my phone didn't have a mouse attached.
Use the Tap event instead of MouseLeftButtonUp. For more complicated gestures, use the Silverlight Toolkit GestureListener class.

Use MouseLeave event
http://vantsuyoshi.wordpress.com/2012/02/01/wp7-mouseleftbuttonup-not-fired-issue/

Related

Xamarin Android: Underline Custom Label Control (make it look like an Entry)

In our application we have some controls that are Entry controls that take in numbers, so we want the user to be able to type into them. Other controls are popup selections such as a Date, or a Picker control, of which we code as Label controls.
To give the user the same consistency as Entry controls, for iOS we put the same frames around them as an Entry control in iOS such as this:
Standard Entry Control Xaml:
<Entry Grid.Row="0" Grid.Column="1" Keyboard="Numeric" ReturnType="Done" WidthRequest="60"
Text="{Binding SocketItem.QuantityPerSys, Converter={StaticResource IntComma}}"
TextChanged="OnIntegerEntryChanging" Placeholder="0"
AutomationId="SocketQtySysValueEntry" />
Mimic'ed Entry Control for iOS Rendering:
<Frame Grid.Row="1" Grid.Column="1" AutomationId="SocketDecisionDateEntryFrame">
<Label Text="{Binding SocketItem.DecisionDate, StringFormat='{0:M/d/yy}', Converter={StaticResource LocalTime}}"
HorizontalTextAlignment="Center" HorizontalOptions="FillAndExpand" AutomationId="SocketDecisionDateValueEntry"/>
<Frame.GestureRecognizers>
<TapGestureRecognizer Tapped="OnSocketDateTapped" CommandParameter="DecisionDate" />
</Frame.GestureRecognizers>
</Frame>
However in Android the Entry controls have a line underneath them:
Android Entry Control
We want to render these label controls the same as the Entry controls so they are consistent:
Label and Entry controls
My assumption is that I have to use a custom renderer of which I already have setup, but with just a normal underline of the text (which is of course what I don't want):
class UnderlinedLabelRenderer : LabelRenderer
{
public UnderlinedLabelRenderer(Context context) : base(context)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<Label> e)
{
base.OnElementChanged(e);
if (Control != null && Control is FormsTextView textView)
{
textView.PaintFlags |= PaintFlags.UnderlineText;
}
}
}
I've tried investigating the actual Xamarin.Forms renderer code for the Entry control, but I'm not familiar enough with it or know exactly which class to look at to figure it out (I'll continue that route in the mean time). I am assuming I'll have to somehow add a new box control to the label cell or something.
I've also looked at this question: Xamarin Forms Android EntryCell Underline which is related but the opposite of what I want to do.
I also tried making the controls disabled Entry controls:
<StackLayout Grid.Row="1" Grid.Column="1" HorizontalOptions="End" AutomationId="OppApplicationValueFrame">
<controls:ReadonlyEntry Text="{Binding Opportunity.Application}" IsEnabled="False"
AutomationId="OppApplicationValueEntry"/>
<StackLayout.GestureRecognizers>
<TapGestureRecognizer Tapped="OnApplicationTapped" />
</StackLayout.GestureRecognizers>
</StackLayout>
but that yielded light gray text (due to the disabled). I then created a custom renderer to fix the text:
protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
{
base.OnElementChanged(e);
if (Control != null && Control is FormsEditText editText)
{
editText.SetTextColor(Android.Graphics.Color.Gray);
//editText.SetBackgroundColor(Android.Graphics.Color.Gray);
}
}
but the underline on the control is still light grey (disabled color). I referenced this question on how to remove the underline: Xamarin Forms Android EntryCell Underline. But doing the opposite of setting it to another color makes the entire cell Gray.
How do I change the color of the underline in the CustomRenderer?
Any help would be great or a place to start looking.
I think the easiest way to achieve your requirement is to use a Entry instead of a Label, disable the edit ability of the entry and add a TapGestureRecognizers there to handle event:
<StackLayout>
<Entry IsEnabled="False" Text="test" HorizontalTextAlignment="Center" />
<StackLayout.GestureRecognizers>
<TapGestureRecognizer Tapped="TapGestureRecognizer_Tapped" NumberOfTapsRequired="1" />
</StackLayout.GestureRecognizers>
</StackLayout>
And I don't think you can achieve this by using a custom renderer of Lable, native TextView does not has this feature.
Another way is to create a custom control, like a view contains a label and a view(height =1) with black background color which stay under the label.
Update:
Code in the custom renderer to change the color of underline:
[assembly: ExportRenderer(typeof(myEntry), typeof(MyEntryRenderer))]
namespace App557.Android
{
class MyEntryRenderer : EntryRenderer
{
public MyEntryRenderer(Context context) : base(context)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
{
base.OnElementChanged(e);
if (Control != null)
{
EditText editText = Control as EditText;
editText.Enabled = false;
editText.SetTextColor(global::Android.Graphics.Color.LightGreen);
editText.Background.SetColorFilter(global::Android.Graphics.Color.LightGreen, PorterDuff.Mode.SrcIn);
editText.SetSingleLine(true);
editText.Ellipsize = TruncateAt.End;
}
}
}
}

Is there a way I can pass a grid name or a parameter into a tapped event?

I have this code and somehow I want to get the text value of the first label to the tapped event in the CS code. As the tapped event is on the grid I had an idea of putting the text value into x:Name.
<ViewCell >
<Grid VerticalOptions="CenterAndExpand" x:Name="{Binding [0].Name}" Tapped="atiSelectValueX" >
<local:StyledLabel Text="{Binding [0].Name}" HorizontalOptions="StartAndExpand" />
<local:StyledLabel IsVisible="{Binding [0].IsSelected}" TextColor="Gray" HorizontalOptions="End" Text="x" />
</Grid>
</ViewCell>
The CS code I have so far:
void atiSelectValueX(object sender, EventArgs e)
{
var cell = sender as Grid;
if (cell == null)
return;
var selected = cell. <<< I want to get the name here
What I would like to do is to get the x:Name value in the CS code. I was hoping to get the sender information but it seems like I cannot enter
cell.Name
Is there another way I can get a parameter like the name (which is the same as the text in the first label always) in the C# code?
The x:Name is metadata used by the XAML tools, not an actual property of the object. You could also bind Name to an unused property, like StyleID, and access that instead
<Grid VerticalOptions="CenterAndExpand" StyleId="{Binding [0].Name}" ...
and then
var selected = cell.StyleId;

Arbitrary Drag and Drop for WP7

I'm trying to find a method of displaying a text block or that will allow me to arbitrarily drag and drop drop that control around the screen.
I've scoured google and here, but every drag and drop related question I find is around exchanging data, not just position.
Is anyone aware of something ready to go, or can you point me in the direction I should be looking?
You can do this by using behaviors:
<TextBlock Text="Hello!">
<i:Interaction.Behaviors>
<el:MouseDragElementBehavior ConstrainToParentBounds="True"/>
</i:Interaction.Behaviors>
</TextBlock>
You need to add a reference to Microsoft.Expression.Interactions in your solution, and the following namespace at the top of your XAML file:
xmlns:el="clr-namespace:Microsoft.Expression.Interactivity.Layout;assembly=Microsoft.Expression.Interactions"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
The xaml:
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<TextBlock Height="30" Margin="125,132,0,0"
Name="textBlock1" Text="TextBlock"
Width="83" MouseMove="textBlock1_MouseMove" />
</Grid>
and the code behind:
private void textBlock1_MouseMove(object sender, MouseEventArgs e)
{
TextBlock realSender = (TextBlock)sender;
var theParent = (Grid)realSender.Parent;
var position = e.GetPosition(theParent);
realSender.Margin = new Thickness(
position.X - realSender.Width / 2,
position.Y - realSender.Height / 2, 0, 0);
}
The toolkit sample used to include an example of doing this.
Not sure if it's still in there though as it was based on the gesture support which has since been deprecated. If it's gone check the August 2011 version.

No DataBinding when rendering a UserControl+ItemsControl in WriteableBitmap?

I want to use a WriteableBitmap to render a programmatically instantiated UserControl to a jpg/png image to use it as a live tile background image in a Windows Phone 7.1 project, but DataBinding is not working as expected when rendering the control.
In general, the UserControl is something like this:
<UserControl>
<Grid x:Name="LayoutRoot" Height="173" Width="173" >
<Grid.Background >
<SolidColorBrush Color="{StaticResource PhoneAccentColor}" />
</Grid.Background >
<Grid.RowDefinitions>
<RowDefinition Height="27"/>
<RowDefinition Height="146"/>
</Grid.RowDefinitions >
<ItemsControl Grid.Row="1" Margin="10,0,0,0" ItemsSource="{Binding}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding MyBindingProperty, FallbackValue=xxx}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<TextBlock Text="Hello World" FontSize="22" Margin="5,0,0,0"/>
<TextBlock TextWrapping="Wrap" Text="{Binding Count, FallbackValue=-1}" FontSize="18.667" Margin="123,0,0,0"/>
</Grid>
</UserControl>
When I now put this control onto a PhoneApplicationPage and assign a list with items of my data structure to the DataContext property of the UserControl, everything works fine and I see one TextBlock for each list item and the Text property of that TextBlock is correctly displaying the value of the property of my data structure. Also the last TextBlock on the Grid displays correctly the current count of list items.
BUT when I'm now trying to programmatically create that UserControl, assign the same list to the DataContext and then use a WriteableBitmap to render it to an image file, it seems that all DataBindings within the DataTemplate of the ItemsControl aren't working anymore, they're displaying the FallbackValue now. Although the DataBinding of the outer TextBlock in the Grid is still working perfectly and also I got the correct number of TextBlocks in the StackPanel (= items in the bound list).
Here is my code for creating the the WriteableBitmap:
var tile = new MyTileControl { DataContext = this._myList };
tile.Arrange(new Rect(0, 0, 173, 173));
tile.Measure(new Size(173, 173));
var bmp = new WriteableBitmap(173, 173);
bmp.Render(tile, null);
bmp.Invalidate();
What's the problem with the DataBindings in the DataTemplate when rendering through a WriteableBitmap and how can I solve it?
I think, that your control not fully created yet, and you can't grab bitmap just after creation. Try to use Dispatcher.BeginInvoke or something else for delayed grabbing of image.
Also, add this control to your page and look where is a problem - in control creation or in bitmap? This give you more information about a problem.

Handling tap on Pushpin in a fixed Map

I have a Map control showing a few Pushpins. I do not want the user to navigate in the map so I disable it. But I do want the user to be able to tap on a Pushpin (and in the event I navigate to a detail page).
However when the Map.IsEnabled is false, the Pushpins don't seem to receive any gestures either. I've also tried using IsHitTestVisible, but with no luck.
Some code showing what I'm trying to do. Does anyone have any ideas?
<maps:Map Name="Map"
VerticalAlignment="Stretch" HorizontalAlignment="Stretch"
CopyrightVisibility="Collapsed" LogoVisibility="Collapsed" ScaleVisibility="Collapsed" ZoomBarVisibility="Collapsed"
IsEnabled="False">
<maps:MapItemsControl ItemsSource="{Binding TheCollection}">
<maps:MapItemsControl.ItemTemplate>
<DataTemplate>
<maps:Pushpin Name="Pin" Location="{Binding Coordinate}" Content="{Binding Ix}">
<maps:Pushpin.Background>
<SolidColorBrush Color="{StaticResource PhoneAccentColor}"/>
</maps:Pushpin.Background>
<toolkit:GestureService.GestureListener>
<toolkit:GestureListener Tap="PinTap" />
</toolkit:GestureService.GestureListener>
</maps:Pushpin>
</DataTemplate>
</maps:MapItemsControl.ItemTemplate>
</maps:MapItemsControl>
</maps:Map>
Setting IsEnabled to false prevents the Map control from responding to user input, which affects the child Pushpin as you've seen. If you want the map to be read-only but the Pushpin to respond to gestures then I think you have two options:
Handle all the gesture events on the Map control and set e.Handled to true, which will prevent the Map itself from processing the event, but should leave the PushPin free to handle the tap gesture.
Create a WriteableBitmap of the Map and show that instead, and then display the Pushpin on top (NOTE: I suspect that the Pushpin control won't work outside of the Map control, so you'd need to create/re-template a control to look like a Pushpin).
UPDATE: The events that you need to handle on the Map to make it appear "read-only" but remain enabled are MapPan and MapZoom.
So here's how I solved it after a lot of testing and browsing MSDN. It turns out that things are a bit different in the Map control on Windows Phone (see MSDN). There are new behaviors and events compared to normal Silverlight.
<maps:Map Name="Map"
VerticalAlignment="Stretch" HorizontalAlignment="Stretch"
CopyrightVisibility="Collapsed" LogoVisibility="Collapsed" ScaleVisibility="Collapsed" ZoomBarVisibility="Collapsed"
MapZoom="Map_MapZoom" MapPan="Map_MapPan">
<maps:MapItemsControl ItemsSource="{Binding TheCollection}">
<maps:MapItemsControl.ItemTemplate>
<DataTemplate>
<maps:Pushpin Name="Pin" Location="{Binding Coordinate}" Content="{Binding Ix}">
<maps:Pushpin.Background>
<SolidColorBrush Color="{StaticResource PhoneAccentColor}"/>
</maps:Pushpin.Background>
<toolkit:GestureService.GestureListener>
<toolkit:GestureListener Tap="PinTap" />
</toolkit:GestureService.GestureListener>
</maps:Pushpin>
</DataTemplate>
</maps:MapItemsControl.ItemTemplate>
</maps:MapItemsControl>
</maps:Map>
...
private void Map_MapPan(object sender, MapDragEventArgs e)
{
e.Handled = true;
}
private void Map_MapZoom(object sender, MapZoomEventArgs e)
{
e.Handled = true;
}

Resources