I would like to add tooltips in Xamarin for UWP by using a native view and the Windows.UI.Xaml Nuget package. I have added the following reference to the xaml page to get the windows native view:
<ContentPage
xmlns:win="clr-namespace:Windows.UI.Xaml.Controls;assembly=Windows, Version=255.255.255.255, Culture=neutral, PublicKeyToken=null, ContentType=WindowsRuntime;targetPlatform=Windows"
</ContentPage>
I can successfully access native windows controls, e.g. a TextBlock:
< win: TextBlock Text="S"/>
However, when I try to add the tooltip to the control:
<win:TextBlock Text="S" ToolTipService.ToolTip="Service agreement"/>
I get the following exception during compilation:
"Xamarin.Forms.Xaml.XamlParseException: 'Position 56:45. Type
ToolTipService not found in xmlns
http://xamarin.com/schemas/2014/forms'"
Is the system getting confused between the standard Windows.UI.Xaml.Controls namespace and the extended one from the Nuget package?
ToolTipService is attached property, and it will not find ToolTipService assembly in Forms client, the better way is use Effect to create your own tip service and render it in UWP platform. You could refer the following steps.
Create a subclass of the PlatformEffect class.
Override the OnAttached method and write logic to customize the control.
Override the OnDetached method and write logic to clean up the control customization, if required.
Add a ResolutionGroupName attribute to the effect class. This attribute sets a company wide namespace for effects, preventing collisions with other effects with the same name. Note that this attribute can only be applied once per project.
Add an ExportEffect attribute to the effect class. This attribute registers the effect with a unique ID that's used by Xamarin.Forms, along with the group name, to locate the effect prior to applying it to a control. The attribute takes two parameters – the type name of the effect, and a unique string that will be used to locate the effect prior to applying it to a control.
UWP Code Part
[assembly: ResolutionGroupName("Microsoft")]
[assembly: ExportEffect(typeof(UWPToolTipEffect), nameof(TooltipEffect))]
namespace NativeSwitch.UWP
{
public class UWPToolTipEffect : PlatformEffect
{
protected override void OnAttached()
{
var control = Control ?? Container;
if (control is DependencyObject)
{
ToolTip toolTip = new ToolTip();
toolTip.Content = TooltipEffect.GetText(Element);
switch (TooltipEffect.GetPosition(Element))
{
case TooltipPosition.Bottom:
toolTip.Placement = Windows.UI.Xaml.Controls.Primitives.PlacementMode.Bottom;
break;
case TooltipPosition.Top:
toolTip.Placement = Windows.UI.Xaml.Controls.Primitives.PlacementMode.Top;
break;
case TooltipPosition.Left:
toolTip.Placement = Windows.UI.Xaml.Controls.Primitives.PlacementMode.Left;
break;
case TooltipPosition.Right:
toolTip.Placement = Windows.UI.Xaml.Controls.Primitives.PlacementMode.Right;
break;
default:
return;
}
ToolTipService.SetToolTip(control, toolTip);
}
}
protected override void OnDetached()
{
}
}
}
Forms code part
public static class TooltipEffect
{
public static readonly BindableProperty TextProperty =
BindableProperty.CreateAttached("Text", typeof(string), typeof(TooltipEffect), string.Empty, propertyChanged: OnTextChanged);
public static readonly BindableProperty PositionProperty =
BindableProperty.CreateAttached("Position", typeof(TooltipPosition), typeof(TooltipEffect), TooltipPosition.Bottom);
public static string GetText(BindableObject view)
{
return (string)view.GetValue(TextProperty);
}
public static void SetText(BindableObject view, string value)
{
view.SetValue(TextProperty, value);
}
public static TooltipPosition GetPosition(BindableObject view)
{
return (TooltipPosition)view.GetValue(PositionProperty);
}
public static void SetPosition(BindableObject view, TooltipPosition value)
{
view.SetValue(PositionProperty, value);
}
static void OnTextChanged(BindableObject bindable, object oldValue, object newValue)
{
var view = bindable as View;
if (view == null)
{
return;
}
string text = (string)newValue;
if (!string.IsNullOrEmpty(text))
{
view.Effects.Add(new ControlTooltipEffect());
}
else
{
var toRemove = view.Effects.FirstOrDefault(e => e is ControlTooltipEffect);
if (toRemove != null)
{
view.Effects.Remove(toRemove);
}
}
}
}
public enum TooltipPosition
{
Bottom,
Right,
Left,
Top
}
class ControlTooltipEffect : RoutingEffect
{
public ControlTooltipEffect() : base($"Microsoft.{nameof(TooltipEffect)}")
{
}
}
Usage
// forms project namesapce
xmlns:effects="clr-namespace:ToolTipTestApp"
......
<win:TextBlock
effects:TooltipEffect.Position="Right"
effects:TooltipEffect.Text="Hello"
Text="Hello Wrold"
/>
Related
I am making a WPF application for sales.
What I want to do is to navigate to diffrent views while choosing whether I keep the navigation history (in case I want to keep a temporary instance of a transaction) or remove one or all history of navigation (in case I want to validate all temporary transactions)
for example, I have a page transaction
there is the case where I want to keep other instances of that page open temperarly or remove all instances if I am done with one or all transaction.
I made a mainwindow that contain a frame .
I want to navigate to a diffrent page inside the frame when a button is clicked while also choosing to keep or remove the navigation history.
What I did so far was implement a window view model class and create a property (PageView) that is a enum that I can convert to the page view I want to show.
The pageview enum
public enum PageView
{
Main=0,
SecondPage=1
}
The converter from property PageView to the page I want to show
public class PageConverter : BaseCoverter<PageConverter>
{
public override object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
switch ((PageView)value)
{
default: Debugger.Break();
return null;
case PageView.Main:
return new MainPage();
case PageView.SecondPage:
return new SecondoryPage();
}
}
public override object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
Main window view model
class WindowVM:BaseViewModel
{
private Window VWindow;
private PageView _pageView= PageView.Main;
public WindowVM(Window window)
{
VWindow = window;
SwitchView = new RelayCommand(_SwitchView, _CheckButton);
}
public PageView CurrentPage
{
set
{
_pageView = value;
OnPropertyChanged();
}
get { return _pageView; ; }
}
public ICommand SwitchView { get; set; }
private void _SwitchView()
{
((WindowVM)((MainWindow)Application.Current.MainWindow).DataContext).CurrentPage = PageView.SecondPage;
}
private bool _CheckButton(object parameter)
{
return true;
}
}
What I have right now is a frame that create a new page each time the command SwitchView is used.
What I want is to choose whether I want to be able to clear the history of the pages I navigated if i wanted to.
I found this solution but I don't want to use code behind to do that.
I also found this solution but I really didn't know how to implement it.
Just wrap the solution with NavigationService.RemoveBackEntry method in the behavior/attached property and apply it to your Frame in xaml.
<Frame local:FrameNoHistoryBehavior.Enable="True" />
And:
public static class FrameNoHistoryBehavior
{
public static bool GetEnable(DependencyObject obj)
{
return (bool)obj.GetValue(EnableProperty);
}
public static void SetEnable(DependencyObject obj, bool value)
{
obj.SetValue(EnableProperty, value);
}
// Using a DependencyProperty as the backing store for Enable. This enables animation, styling, binding, etc...
public static readonly DependencyProperty EnableProperty =
DependencyProperty.RegisterAttached("Enable",
typeof(bool),
typeof(FrameNoHistoryBehavior),
new UIPropertyMetadata(false, OnEnablePropertyChanged));
private static void OnEnablePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (!(d is Frame frame))
throw new NotSupportedException($"This behavior supports only {nameof(Frame)}");
if((bool)e.NewValue)
{
frame.Navigated += OnFrameNavigated;
}
else
{
frame.Navigated -= OnFrameNavigated;
}
void OnFrameNavigated(object sender, NavigationEventArgs e)
{
frame.NavigationService.RemoveBackEntry();
}
}
}
I'm having a small issue modding the code below. It works fine except that I have to set the typeface permanently in the nIcon class. I want to use it for different typefaces from my xaml file.
In the example code below, I want the typeface to change from the default "FontAwesome" to the "Ionicons" as stated in the xaml file.
Since Xaml does not accept parameters when creating the class, I have no idea how to set it.
Any ideas?
Xaml Code:
<controls:nIcon
Text="\nf011"
FontFamily="Ionicons"/>
Shared code:
public class nIcon : Label
{
public const string Typeface = "FontAwesome";
public nIcon()
{
\\ FontFamily = Typeface;
}
}
Android Renderer:
public class IcDroidRenderer : LabelRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Label> e)
{
base.OnElementChanged(e);
if (e.OldElement == null)
{
Control.Typeface = Typeface.CreateFromAsset(Forms.Context.Assets, nIcon.Typeface + ".ttf");
}
}
}
I am using Xamarin Forms picker control and require setting the text color, however there is no such property. I have tried making a custom renderer which worked out for me in android and ios (I ended up redrawing the control). In the wp8.1 platform there is no Draw event and the control itself in the renderer doesn't seem to have the properties to set the text color. I have also attempted changing the control the picker binds to unsuccessfully.
Currently I have created the bindable property TextColor in the PCL which is working. The code for my renderer is shown below (I have stripped all my test code and am putting only the basic code since I havent found anything useful yet and am putting my code just to keep everyone in context). Also note that the property Picker.TextColorProperty doesnt exist and is what I would like to do...
using Namespace.CustomControls;
using Namespace.WinPhone.Renderers;
using Xamarin.Forms;
using Xamarin.Forms.Platform.WinPhone;
[assembly: ExportRendererAttribute(typeof(BindablePicker), typeof(BindablePickerRenderer))]
namespace Namspace.WinPhone.Renderers
{
public class BindablePickerRenderer : PickerRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Picker> e)
{
base.OnElementChanged(e);
var picker = e.NewElement;
BindablePicker bp = (BindablePicker)this.Element;
if (this.Control != null)
{
var pickerStyle = new Style(typeof(Picker))
{
Setters = {
new Setter {Property = Picker.BackgroundColorProperty, Value = bp.BackgroundColor},
new Setter {Property = Picker.TextColorProperty, Value = bp.TextColor}
}
};
picker.Style = pickerStyle;
}
}
}
}
Anyhow I am wondering if anyone might have a little more knowledge on how to do this and could shed some light on me.
There is no TextColor property available in the Picker like you mention.
Even this being the case, we can still achieve changing the Picker text color for WindowsPhone.
I'm assuming you are inheriting from PickerRenderer as it was missing from your code example and I've added some extra things so this is more helpful to others:-
Define the interface in the PCL:-
public interface ICustomPicker2
{
Xamarin.Forms.Color MyBackgroundColor { get; set; }
Xamarin.Forms.Color MyTextColor { get; set; }
}
Extend the Xamarin.Forms Picker in the PCL:-
public class CustomPicker2
: Xamarin.Forms.Picker
, ICustomPicker2
{
public static readonly BindableProperty MyBackgroundColorProperty = BindableProperty.Create<CustomPicker2, Xamarin.Forms.Color>(p => p.MyBackgroundColor, default(Xamarin.Forms.Color));
public static readonly BindableProperty MyTextColorProperty = BindableProperty.Create<CustomPicker2, Xamarin.Forms.Color>(p => p.MyTextColor, default(Xamarin.Forms.Color));
public Xamarin.Forms.Color MyTextColor
{
get { return (Xamarin.Forms.Color)GetValue(MyTextColorProperty); }
set { SetValue(MyTextColorProperty, value); }
}
public Xamarin.Forms.Color MyBackgroundColor
{
get { return (Xamarin.Forms.Color)GetValue(MyBackgroundColorProperty); }
set { SetValue(MyBackgroundColorProperty, value); }
}
}
Create your WindowsPhone renderer like so in a class library:-
public class CustomPicker2Renderer
: PickerRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Picker> e)
{
base.OnElementChanged(e);
var picker = e.NewElement;
CustomPicker2 bp = (CustomPicker2)this.Element;
if (this.Control != null)
{
var pickerStyle = new Style(typeof(Picker))
{
Setters = {
new Setter {Property = Picker.BackgroundColorProperty, Value = bp.MyBackgroundColor},
}
};
SetPickerTextColor(bp.MyTextColor);
picker.Style = pickerStyle;
}
}
private void SetPickerTextColor(Xamarin.Forms.Color pobjColor)
{
byte bytR = (byte)(pobjColor.R * 255);
byte bytG = (byte)(pobjColor.G * 255);
byte bytB = (byte)(pobjColor.B * 255);
byte bytA = (byte)(pobjColor.A * 255);
//
((System.Windows.Controls.Control)(((System.Windows.Controls.Panel)this.Control).Children[0])).Foreground = new SolidColorBrush(System.Windows.Media.Color.FromArgb(bytA, bytR, bytG, bytB));
}
Note, the above is all what you need if you just want to set the text color the once.
However if you want to change the color after it has been initially set, then you will need to listen to the property change and act upon it like in the following:-
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
//
if (e.PropertyName == "MyTextColor")
{
SetPickerTextColor((this.Element as CustomPicker2).MyTextColor);
}
}
You will also need to export the renderer from the class library as well:-
[assembly: ExportRendererAttribute(typeof(CustomPicker2), typeof(CustomPicker2Renderer))]
I wrote User Control (yay!). But I want it to behave as a container. But wait! I know about
[Designer("System.Windows.Forms.Design.ParentControlDesigner, System.Design",
typeof(IDesigner))]
Trick.
The problem is - I don't want all of my control to behave like container, but only one part. One - de facto - panel ;)
To give wider context: I wrote a control that has Grid, some common buttons, labels and functionalities. But it also has a part where the user is supposed to drop his custom buttons/controls whatever. Only in this particular part of the control, nowhere else.
Anyone had any idea?
You should do the following :
For your user control, you need to create a new designer which enables the inner panel on design-time by calling EnableDesignMode method.
For the inner panel, you need to create a designer which disables moving, resizing and removes some properties from designer.
You should register the designers.
Example
You can read a blog post about this topic here and clone or download a working example:
r-aghaei/ChildContainerControlDesignerSample
Download Zip
Code
Here is the code for different elements of the solution.
Your user control
[Designer(typeof(MyUserControlDesigner))]
public partial class MyUserControl : UserControl
{
public MyUserControl()
{
InitializeComponent();
TypeDescriptor.AddAttributes(this.panel1,
new DesignerAttribute(typeof(MyPanelDesigner)));
}
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public Panel ContentsPanel
{
get { return panel1; }
}
}
Designer for the inner panel
public class MyPanelDesigner : ParentControlDesigner
{
public override SelectionRules SelectionRules
{
get
{
SelectionRules selectionRules = base.SelectionRules;
selectionRules &= ~SelectionRules.AllSizeable;
return selectionRules;
}
}
protected override void PostFilterAttributes(IDictionary attributes)
{
base.PostFilterAttributes(attributes);
attributes[typeof(DockingAttribute)] =
new DockingAttribute(DockingBehavior.Never);
}
protected override void PostFilterProperties(IDictionary properties)
{
base.PostFilterProperties(properties);
var propertiesToRemove = new string[] {
"Dock", "Anchor", "Size", "Location", "Width", "Height",
"MinimumSize", "MaximumSize", "AutoSize", "AutoSizeMode",
"Visible", "Enabled",
};
foreach (var item in propertiesToRemove)
{
if (properties.Contains(item))
properties[item] = TypeDescriptor.CreateProperty(this.Component.GetType(),
(PropertyDescriptor)properties[item],
new BrowsableAttribute(false));
}
}
}
Designer for your user control
public class MyUserControlDesigner : ParentControlDesigner
{
public override void Initialize(IComponent component)
{
base.Initialize(component);
var contentsPanel = ((MyUserControl)this.Control).ContentsPanel;
this.EnableDesignMode(contentsPanel, "ContentsPanel");
}
public override bool CanParent(Control control)
{
return false;
}
protected override void OnDragOver(DragEventArgs de)
{
de.Effect = DragDropEffects.None;
}
protected override IComponent[] CreateToolCore(ToolboxItem tool, int x,
int y, int width, int height, bool hasLocation, bool hasSize)
{
return null;
}
}
I need a WPF control that functions similar to the 'Resolve Conflicts' window in TFS, and other similar source control systems.
I have the following classes
public class Conflict:INotifyPropertyChanged
{
private string _name;
private List<Resolution> _resolutions;
private bool _focused;
private bool _hasResolutions;
public string Name
{
get { return _name; }
set
{
_name = value;
OnPropertyChanged("Name");
}
}
public List<Resolution> Resolutions
{
get { return _resolutions; }
set
{
_resolutions = value;
OnPropertyChanged("Resolutions");
}
}
public bool Focused
{
get { return _focused; }
set {
_focused = value;
OnPropertyChanged("Focused");
}
}
public bool HasResolutions
{
get { return _resolutions.Any(); }
set
{
_hasResolutions = value;
OnPropertyChanged("HasResolutions");
}
}
}
public class Resolution
{
public string Name { get; set; }
public void Resolve()
{
//Logic goes here
}
}
This almost identical to the functionality of the Team Foundation Server (TFS) 'Resolve Conflict' window shown below:
For each row in the image above, it is the same as my Conflcit object, and for each of the buttons, would be one of the Resolution objects on the Conflict object.
My plan was to bind my List to a ListView, and then write a custom template or whatever to hide/show the buttons below it based on if it was selected or not.
To try to simplify what I need to accomplish, I have a List and I want to bind it to a control, and it look as close to the image above as possible.
How would I accomplish this and XAML and the code behind?
Here is an example of how you can dynamically create a data template, and add buttons based on your Conflict objects:
public DataTemplate BuildDataTemplate(Conflict conflict)
{
DataTemplate template = new DataTemplate();
// Set a stackpanel to hold all the resolution buttons
FrameworkElementFactory factory = new FrameworkElementFactory(typeof(StackPanel));
template.VisualTree = factory;
// Iterate through the resolution
foreach (var resolution in conflict.Resolutions)
{
// Create a button
FrameworkElementFactory childFactory = new FrameworkElementFactory(typeof(Button));
// Bind it's content to the Name property of the resolution
childFactory.SetBinding(Button.ContentProperty, new Binding("Name"));
// Bind it's resolve method with the button's click event
childFactory.AddHandler(Button.ClickEvent, new Action(() => resolution.Resolve());
// Append button to stackpanel
factory.AppendChild(childFactory);
}
return template;
}
You can do this in many different ways and this is just one of them.
I haven't test it, but this should be enough to get you started :)
Good luck