How to disallow interaction on a view behind uipopovercontroller - c#

I have a UIPopoverController that I am using and I have two buttons each displays a popup when clicked. However, I do not want the popup to be displayed at the same time - meaning I do not want the user to be able to press the one button and while the popup is displayed be able to press the other button. It seems like I have tried everything - disabling the user interaction on the buttons, hiding the view behind the pop up, using passthrough views for the pop and more. None of it works! The disabling of the user interaction seems to work for the most part but then stops disallowing the user to interact with the button and causes the application to crash...
popupView.PassthroughViews = new UIView[]{this.View.Superview, this.View, this.Gray}; //gray is another view that sits under the view that calls the popup
this.View.UserInteractionEnabled = false;
this.PositiveMeterBtn.UserInteractionEnabled = false;
this.View.Hidden = true;
My UIPopoverController is declared at the class level and I have even done code like this:
if(popupView != null)
return;
I still get multiple popups. I am using mono touch/xamarin - is this a bug with xamarin or an ios issue? Am I handling this in the correct manner?

I haven't worked with Xamarin before, but what's worked for me in native Objective-C is
[controller setModalInPopover:YES];
where controller is the view controller displayed within the popover.
From the UIViewController class reference:
#property(nonatomic, readwrite, getter=isModalInPopover) BOOL modalInPopover
The default value of this property is NO. Setting it to YES causes an owning popover controller to disallow interactions outside this view controller while it is displayed.

You can either make the popover modal but if it doesn't contain content that is meant to be modal, you shouldn't block the user.
Usually the better option is to make two helper methods and place them for instance in your app delegate. The methods take care that an existing popover is dismissed if another one is to be shown. This way you will have a maximum of on UIPopoverController and don't have to worry about dismissal.
/// <summary>
/// Shows a popover.
/// </summary>
/// <param name='controllerToShow'>the controller to show in the popover</param>
/// <param name='showFromRect'>the rectangle to present the popover from. Not used if showFromItem is specified.</param>
/// <param name='showInView'>the view the popover is hosted in</param>
/// <param name='showFromItem'>the bar button item the popover gets presented from.</param>
/// <param name='popoverContentSize'>the content size of the popover</param>
/// <param name='animated'>If set to <c>true</c>, animated the popover</param>
/// <param name='arrowDirection'>the allowed arrow directions</param>
/// <param name='onDismiss'>callback if the popover gets dismissed. Careful that the object that owns the callback doesn't outlive the popover controller to prevent uncollectable memory.</param>
public static void ShowPopover(UIViewController controllerToShow, RectangleF showFromRect, UIView showInView, UIBarButtonItem showFromItem, SizeF popoverContentSize, bool animated = true, UIPopoverArrowDirection arrowDirection = UIPopoverArrowDirection.Any, EventHandler onDismiss = null)
{
if(AppDelegateBase.popoverController != null)
{
AppDelegateBase.DismissPopover(false);
}
if(showFromItem == null && showFromRect.IsEmpty)
{
// Nothing to attach the popover to.
return;
}
popoverController = new UIPopoverController(controllerToShow);
if(!popoverContentSize.IsEmpty)
{
popoverController.SetPopoverContentSize(popoverContentSize, false);
}
if(onDismiss != null)
{
popoverController.DidDismiss += onDismiss;
}
// Send a notification that a popover will be presented.
NSNotificationCenter.DefaultCenter.PostNotificationName("WillPresentPopover", popoverController);
if(showFromItem != null)
{
popoverController.PresentFromBarButtonItem(showFromItem, arrowDirection, animated);
}
else
{
popoverController.PresentFromRect(showFromRect, showInView, arrowDirection, animated );
}
}
/// <summary>
/// Dismisses the popover presented using ShowPopover().
/// </summary>
/// <param name='animated'>If set to <c>true</c>, animates the dismissal</param>
public static void DismissPopover(bool animated = false)
{
if(popoverController != null)
{
popoverController.Dismiss(animated);
}
AppDelegateBase.popoverController = null;
}
private static UIPopoverController popoverController;

One thing you might try is using the method
-(BOOL)shouldPerformSegueWithIdentifier:(NSString *)identifier sender:(id)sender
And in that method check if one of your popover view controller's is on screen.
if (popupView.view.window) {
return NO;
} else {
return YES;
}

Related

How can you store a Control in a class and reuse it in a binding?

I am trying to reuse NumberBoxes for a GridView because having the NumberBoxes embedded directly in the GridView data template causes undesirable behavior, while reusing them does not. The problem is that I keep getting exceptions. They say "No installed components were detected" on the following line (templateRoot.FindName("NumberBox") as GridViewItem).Content = item.NumberBox; in this context
/// <summary>
/// The callback for updating a container in the GridView named CardGridView.
/// </summary>
/// <param name="sender"></param>
/// <param name="args"></param>
private void UpdateGridViewContainer(ListViewBase sender, ContainerContentChangingEventArgs args)
{
if (args.Phase == 1)
{
Grid templateRoot = args.ItemContainer.ContentTemplateRoot as Grid;
CardItem item = args.Item as CardItem;
(templateRoot.FindName("NumberBox") as GridViewItem).Content = item.NumberBox;
TypedEventHandler<NumberBox, NumberBoxValueChangedEventArgs> handler =
(box, args) =>
{
if (!double.IsNaN(args.NewValue))
{
_viewModel.ChangeCount(args, item);
}
};
item.SetHandler(handler);
}
}
The exception is thrown when the Page that contains the GridView is left and renavigated to. I have tried nulling out the NumberBoxes when the page is left, but that did not work. Well, it appeared to before the issue cropped up again.
This is the code that nulls out the NumberBoxes
/// <summary>
/// Creates new NumberBoxes for when this Page is loaded again.
/// </summary>
private void ResetNumberBoxes()
{
foreach (CardItem card in CardGridView.Items.Cast<CardItem>())
{
card.ResetNumberBox();
}
CardGridView.ItemsSource = null;
CardGridView.Items.Clear();
}
ResetNumberBox is just setting the NumberBox to null and assigning a new one.
The exception details
System.Runtime.InteropServices.COMException
HResult=0x800F1000
Message=No installed components were detected. (0x800F1000)
Source=WinRT.Runtime
StackTrace:
at WinRT.ExceptionHelpers.<ThrowExceptionForHR>g__Throw|20_0(Int32 hr)
at ABI.Microsoft.UI.Xaml.Controls.IContentControlMethods.set_Content(IObjectReference _obj, Object value)
An update. I have removed the GridViewItem control from the DataTemplate and tried doing the following with the same result
CardItem item = args.Item as CardItem;
Grid.SetColumn(item.NumberBox, 1);
item.NumberBox.HorizontalAlignment = HorizontalAlignment.Center;
item.NumberBox.VerticalAlignment = VerticalAlignment.Center;
(templateRoot.FindName("GridViewTemplate") as Grid).Children.Add(item.NumberBox);
I also examined a heap dump right before the line that throws, and there was only one instance of the Grid, and the NumberBox had a parent of null.

How to display the MonthCalendar control on top of Panels?

How can I display the MonthCalendar on top similar to the DateTimePickerdrop down? I have a
a custom control (TextBox and Button) that displays the MonthCalendar dynamically when clicked.
I was able to get it in front of the controls with the panel but not over the panel. Image below;
private void btn_Click(object sender, EventArgs e)
{
if (mc.Parent == null)
{
this.Parent.Controls.Add(mc);
mc.Location = new Point(this.Location.X, this.Location.Y + this.Height);
mc.BringToFront();
}
else
{
mc.Show();
}
}
The drop-down calendar on a DateTimePicker is a floating window, as is the drop-down list on a ComboBox. The MonthCalendar is just a regular control so, just like any other control, it cannot be displayed outside the bounds of its parent. If you want it displayed over a form and outside the bounds of a Panel then you'd need to make its parent the form, rather than the Panel. If you want it to be displayed outside the bounds of the form too then you'd need to create a new, borderless form and make that the control's parent.
Here is a method you can use to move a child control from one parent to another without changing its absolute position on screen:
/// <summary>
/// Moves a child control from its current parent to a new parent while maintaining the same absolute location.
/// </summary>
/// <param name="child">
/// The child control to move.
/// </param>
/// <param name="newParent">
/// The new parent to move the child control to.
/// </param>
private void ChangeParent(Control child, Control newParent)
{
var screenLocation = child.PointToScreen(Point.Empty);
child.Parent = null;
child.Location = newParent.PointToClient(screenLocation);
child.Parent = newParent;
}
In your case, you probably want to call that like so:
ChangeParent(mc, this);
You may need to call BringToFront on the child control afterwards, depending on your z-order.
Note that you can also just add the MonthCalendar to the form in the designer and then change its Location in the properties window to get it in the right place.

Creating a MouseEnter Event for every Button in a window

I've been working on an application months ago, my friend and i wanted to share this application with other friends, and i need to display some help to make the application easier because it was designed only for both of us.
The idea that came out is to display help on a text Block every time a hover event is popped from a button. So we added a textBlock. Now the problem that we still facing is how to create the Hover Event for every button in our Main Window, there are a lots of buttons in this window, So we can't add an event to every button in the XAML code.
What i am expecting from this answer is a way to add Hover Event to all buttons in the main window Programmatically ?
EDIT: after some googling and help, i can do the following :
foreach (UIElement btn in GeneralMenuGrid.Children)
{
if (btn is Button)
{
Button currentButton = (Button)btn;
currentButton.Content = "test";
}
}
This is just a test that will allow all the buttons in the GeneralMenuGrid control to have a content : test, now the problem again is that i have nested controls in this grid, how can i reach them?
EDIT : after years of goggling i got to loop through all the buttons in my window with this :
public static void EnumVisuals(Visual argVisual, Window currentWindow)
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(argVisual); i++)
{
Visual childVisual = (Visual) VisualTreeHelper.GetChild(argVisual, i);
if (childVisual is Button)
{
var button = childVisual as Button;
button.MouseEnter += AllButtonsOnMouseEnter;
}
EnumVisuals(childVisual, currentWindow);
}
}
now in the AllButtonsOnMouseEnter function, i can't access a button, i made it public... i can't access it from this class, how can i send the window with the event arguments?
You wrote, "there are a lots of buttons in this window, so we can't add an event to every button in the XAML code." But you can - just add a style that applies to all buttons:
<Style TargetType="{x:Type Button}">
<EventSetter Event="MouseEnter" Handler="Button_MouseEnter"/>
</Style>
I don't know how you intend to get the help text relevant to each Button, but it's easy to store it in the Button's Tag:
<Button Tag="Instantly move from one place to another.">
Teleport
</Button>
Then write an event handler that shows the help in your TextBlock:
private void Button_MouseEnter(object sender, MouseEventArgs e)
{
Button button = sender as Button;
textblock_that_shows_help.Text = button.Tag;
}
I've created an extension method that does this, adapted from here: Find all controls in WPF Window by type
Put this class somewhere in your project:
public static class VisualTreeSearch
{
/// <summary>
/// Finds all elements of the specified type in the <see cref="System.Windows.DependencyObject"/>'s visual tree using a breadth-first search.
/// </summary>
/// <typeparam name="T">The type of element to search for.</typeparam>
/// <param name="root">The object to search in.</param>
/// <returns>A list of elements that match the criteria.</returns>
public static IEnumerable<T> Find<T>(this DependencyObject root) where T : DependencyObject
{
return root.Find<T>(false, true);
}
/// <summary>
/// Finds all elements of the specified type in the <see cref="System.Windows.DependencyObject"/>'s visual tree.
/// </summary>
/// <typeparam name="T">The type of element to search for.</typeparam>
/// <param name="root">The object to search in.</param>
/// <param name="depthFirst">True to do a depth-first search; false to do a breadth-first search</param>
/// <param name="includeRoot">True to include the root element in the search; false to exclude it</param>
/// <returns>A list of elements that match the criteria.</returns>
public static IEnumerable<T> Find<T>(this DependencyObject root, bool depthFirst, bool includeRoot) where T : DependencyObject
{
if (includeRoot)
{
var depRoot = root as T;
if (depRoot != null)
yield return depRoot;
}
var searchObjects = new LinkedList<DependencyObject>();
searchObjects.AddFirst(root);
while (searchObjects.First != null)
{
var parent = searchObjects.First.Value;
var count = VisualTreeHelper.GetChildrenCount(parent);
var insertAfterNode = depthFirst ? searchObjects.First : searchObjects.Last;
for (int i = 0; i < count; i++)
{
DependencyObject child = VisualTreeHelper.GetChild(parent, i);
var depChild = child as T;
if (depChild != null)
yield return depChild;
insertAfterNode = searchObjects.AddAfter(insertAfterNode, child);
}
searchObjects.RemoveFirst();
}
}
}
To use the extension method, write the following in your window class (as an example). This code loops through all children, grandchildren, etc. of this (which should be your Window in this case) and finds all Button elements.
foreach (var button in this.Find<Button>())
{
button.MouseEnter += button_MouseEnter;
}
...
private void button_MouseEnter(object sender, MouseEventArgs e)
{
// Do stuff
}
after a lot of googling and chat help... i finally did it, maybe other will be interested this is how i proceeded :
i created a static recursive function that will get all the buttons in the window:
public static void EnumVisuals(Visual argVisual, Button toModifyButton)
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(argVisual); i++)
{
Visual childVisual = (Visual) VisualTreeHelper.GetChild(argVisual, i);
if (childVisual is Button)
{
var button = childVisual as Button;
button.MouseEnter += (sender, eventArgs) =>
{
toModifyButton.Content = "Get the Help text from database if you want";
};
}
EnumVisuals(childVisual, toModifyButton);
}
}
why do you send a Button :
i need to write the help in a button, and the only way i found to access it's content property is to send it via this function and of course make it public.
Hope you'll find this helpfull.

How to localize DatePicker and TimePicker in WP8

I am attempting to localize the DatePicker and TimePicker associated with the toolkit on WP7, but I am unsure of how to access the Header and the application bar texts. I have not been able to find any links showing methods of accomplishing these tasks. Are there any useful links or does someone have the source of how this many be accomplished?
Easiest method to download Source and Samples of the latest version of the Toolkit (Nov 2011) which has by default localization for DatePicker and TimePicker.
Add the it as project reference to your solution.
If you have the Toolkit version prior to Nov 2011,
Again add it as Project Reference in your Solution
In side the project tookit in your solution. Add the necessary resx files. You can see there is a default Resources.resx file which has the English text for date pickers. Add the necessary resx files for other languages.
It's very simple: Parameter - Language. Xaml code:
<toolkit:DatePicker Language="ru-RU" Margin="-12, 0" Value="{Binding BirthDate, Mode=TwoWay}" />
Another alternative without modifying the XAML source is to modify the "HeaderTitle" TextBlock once the page loads.
/// <summary>
/// Called from app.xaml.cs if the user navigates to the DatePickerPage
/// </summary>
/// <param name="page">The page.</param>
public static void DatePickerHook(PhoneApplicationPage page)
{
// Somehow modify the text on the top of the page...
LoopThroughControls(page, (ui => {
var tb = ui as TextBlock;
if (tb != null && tb.Name == "HeaderTitle")
{
tb.Text = "<<Local Translation>>";
}
}));
}
/// <summary>
/// Applies an action to every element on a page
/// </summary>
/// <param name="parent">The parent.</param>
/// <param name="modifier">The modifier.</param>
private static void LoopThroughControls(UIElement parent, Action<UIElement> modifier)
{
int count = VisualTreeHelper.GetChildrenCount(parent);
if (count > 0)
{
for (int i = 0; i < count; i++)
{
UIElement child = (UIElement)VisualTreeHelper.GetChild(parent, i);
modifier(child);
LoopThroughControls(child, modifier);
}
}
return;
}
Here's the link to the blog post that describes the modifications to app.xaml.cs also: http://blog.dotnetframework.org/2015/11/09/localise-datepicker-in-wp8-silverlighttoolkit-using-hooks/

How can I make a WPF Image disableable?

I need an Image that is grayed out when disabled (IsEnabled=False). A grayed out version of the image can be produced by reading the BitmapImage into a FormatConvertedBitmap which is shown here.
I have been able to get this working with a UserControl but now I would like the same behavior in a specialized Image class for more flexibility. I don't care if this is implemented in XAML, code-behind or both, but it needs to be a subclass of Image.
The usage could be:
<DisableableImage Source="Images/image1.png" />
<DisableableImage Source="Images/image1.png" IsEnabled="False" />
<!-- Since IsEnabled is inherited down the tree,
the image will be grayed out like the rest of the button -->
<Button IsEnabled="False">
<StackPanel Orientation="Horizontal">
<TextBlock>OK</TextBlock>
<DisableableImage Source="Images/ok.png" />
</StackPanel>
</Button>
Have a look at this link
EDIT:
Or this one (all you need is the AutoGreyableImage class)
I made a little comparison based on the following solutions.
The approaches in the link provided by the OP
The links provided by Thomas Levesque
AutoDisabledImage
AutoGreyableImage
Greyscale Effect
Since I already had a licens for the Infragistics Net Advantage for WPF it was easy to try it out
Here is the result
So the best approach depends on what results you are after. As for me, I think the result produced by AutoDisabledImage from Infragistics is too bright, AutoGreyableImage does a pretty good job (Identical result to Approach 1 (OP link)) and GreyscaleEffect produces the best result.
if you use this a lot consider creating a custom Effect introduced with .NET 3.5 SP1 (not bitmapeffect) to render such an operation on your GPU. this effect can then be easily controlled by triggers.
More complete version of the AutoGreyableImage by Thomas Lebrun. For anyone interested, I started using Thomas Lebruns class and ran into a couple of nullreference exceptions, as well as finding out that an image would not be disabled if the isEnabled property was set first and the source set after.
So here's the class that finally did the trick for me. À propos, you can of course add the matter of opacity into this, but I decided to leave that up to the xaml around the image.
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media.Imaging;
using System.Windows.Media;
namespace MyDisabledImages
{
/// <summary>
/// Class used to have an image that is able to be gray when the control is not enabled.
/// Based on the version by Thomas LEBRUN (http://blogs.developpeur.org/tom)
/// </summary>
public class AutoGreyableImage : Image
{
/// <summary>
/// Initializes a new instance of the <see cref="AutoGreyableImage"/> class.
/// </summary>
static AutoGreyableImage()
{
// Override the metadata of the IsEnabled and Source property.
IsEnabledProperty.OverrideMetadata(typeof(AutoGreyableImage), new FrameworkPropertyMetadata(true, new PropertyChangedCallback(OnAutoGreyScaleImageIsEnabledPropertyChanged)));
SourceProperty.OverrideMetadata(typeof(AutoGreyableImage), new FrameworkPropertyMetadata(null, new PropertyChangedCallback(OnAutoGreyScaleImageSourcePropertyChanged)));
}
protected static AutoGreyableImage GetImageWithSource(DependencyObject source)
{
var image = source as AutoGreyableImage;
if (image == null)
return null;
if (image.Source == null)
return null;
return image;
}
/// <summary>
/// Called when [auto grey scale image source property changed].
/// </summary>
/// <param name="source">The source.</param>
/// <param name="args">The <see cref="System.Windows.DependencyPropertyChangedEventArgs"/> instance containing the event data.</param>
protected static void OnAutoGreyScaleImageSourcePropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs ars)
{
AutoGreyableImage image = GetImageWithSource(source);
if (image != null)
ApplyGreyScaleImage(image, image.IsEnabled);
}
/// <summary>
/// Called when [auto grey scale image is enabled property changed].
/// </summary>
/// <param name="source">The source.</param>
/// <param name="args">The <see cref="System.Windows.DependencyPropertyChangedEventArgs"/> instance containing the event data.</param>
protected static void OnAutoGreyScaleImageIsEnabledPropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs args)
{
AutoGreyableImage image = GetImageWithSource(source);
if (image != null)
{
var isEnabled = Convert.ToBoolean(args.NewValue);
ApplyGreyScaleImage(image, isEnabled);
}
}
protected static void ApplyGreyScaleImage(AutoGreyableImage autoGreyScaleImg, Boolean isEnabled)
{
try
{
if (!isEnabled)
{
BitmapSource bitmapImage = null;
if (autoGreyScaleImg.Source is FormatConvertedBitmap)
{
// Already grey !
return;
}
else if (autoGreyScaleImg.Source is BitmapSource)
{
bitmapImage = (BitmapSource)autoGreyScaleImg.Source;
}
else // trying string
{
bitmapImage = new BitmapImage(new Uri(autoGreyScaleImg.Source.ToString()));
}
FormatConvertedBitmap conv = new FormatConvertedBitmap(bitmapImage, PixelFormats.Gray32Float, null, 0);
autoGreyScaleImg.Source = conv;
// Create Opacity Mask for greyscale image as FormatConvertedBitmap does not keep transparency info
autoGreyScaleImg.OpacityMask = new ImageBrush(((FormatConvertedBitmap)autoGreyScaleImg.Source).Source); //equivalent to new ImageBrush(bitmapImage)
}
else
{
if (autoGreyScaleImg.Source is FormatConvertedBitmap)
{
autoGreyScaleImg.Source = ((FormatConvertedBitmap)autoGreyScaleImg.Source).Source;
}
else if (autoGreyScaleImg.Source is BitmapSource)
{
// Should be full color already.
return;
}
// Reset the Opcity Mask
autoGreyScaleImg.OpacityMask = null;
}
}
catch (Exception)
{
// nothin'
}
}
}
}
Create a DisableableImage class that is a typical WPF control. Inside, place two elements: the image, and a rectangle that appears only when the control is disabled. The rectangle should be the same width and height as the image, and it should overlay the image. With a color of gray and an alpha of somewhere around 40%, you should get an effect similar to actually graying out the image -- without all the effort to actually modify the image itself.

Categories

Resources