I have been trying to change the color of UItextfield placeholder and I have had no luck. I have tried sub classing, and I have tried modifying the Attributed string but nothing worked.
This is the subclass I tried.
public class CustomTextbox : UITextField
{
private string placeholder{ get; set; }
public CustomTextbox ()
{
}
public CustomTextbox(string theString)
{
this.placeholder = theString;
}
public override void DrawPlaceholder (RectangleF rect) {
using (UIFont font = UIFont.SystemFontOfSize (16))
using (UIColor col = new UIColor (UIColor.Blue.CGColor)) {
col.SetFill (); col.SetStroke (); col.SetColor (); base.DrawString (base.Placeholder,rect, font); } }
}
This is the attributed string method I tried:
var textat = new NSMutableAttributedString ("Email", new CTStringAttributes () {
ForegroundColor = UIColor.White.CGColor,StrokeColor = UIColor.White.CGColor,
StrokeWidth = 5.0f
});
this.emailtextbox.AttributedPlaceholder = textat;
Just simplify what you already have.
This works on iOS7:
var t = new UITextField()
{
AttributedPlaceholder = new NSAttributedString("some placeholder text", null, UIColor.Red)
}
The monotuch way to do this is (the answer is found here)
myLogin.AttributedPlaceholder = new NSAttributedString (
"Enter your credentials",
font: UIFont.FromName ("HoeflerText-Regular", 24.0f),
foregroundColor: UIColor.Red,
strokeWidth: 4
);
Try this one:
[yourtextField setValue:[UIColor blueColor] forKeyPath:#"_placeholderLabel.textColor"];
Related
I'm building a card game and I'd like to have a clean architecture for card abilities.
I've got a CardData ScriptableObject with properties for a card. I want card abilities to compose together to describe what a card does, like a card called DrawAndHealCard that draws 2 cards and heals 5 health when played.
I realized right away that this means I'll need a concrete asset for each variation of a CardAbility. So DrawAndHealCard has a reference to two assets: DrawCards2 and HealPlayer5. That's ridiculous, I want all the data to feel like it's on a single DrawAndHealCard.
So I learned about AssetDatabase.AddObjectToAsset(), this seems like the right idea, I can have the abilities as sub-assets of a CardData asset and not have deal with the organization of all these separate assets. So now I'm trying to build an Editor to manage this and it is painful.
I've read so much stuff about Unity serialization, SOs, Editor scripts, ... seriously hitting a wall with this and about to downgrade to something that feels less elegant architecturally. If there's a better way to do this, I'm open to suggestions about totally different routes too.
Code below is stripped down, but it's the gist of what I'm trying to figure out.
Where I'm at right now is onAddCallback seems to be adding a sub-asset correctly, but onRemoveCallback does not remove it. My Clear All Abilities button does work however. I can't find any good docs or guides about this stuff, so I'm pretty lost currently.
// CardData.cs
[CreateAssetMenu(fileName = "CardData", menuName = "Card Game/CardData", order = 1)]
public class CardData : ScriptableObject
{
public Sprite image;
public string description;
public CardAbility[] onPlayed;
}
// CardAbility.cs
public class CardAbility : ScriptableObject
{
public abstract void Resolve();
}
// DrawCards.cs
public class DrawCards : CardAbility
{
public int numCards = 1;
public override void Resolve()
{
Deck.instance.DrawCards(numCards);
}
}
// HealPlayer.cs
public class HealPlayer : CardAbility
{
public int healAmt = 10;
public override void Resolve()
{
Player.instance.Heal(healAmt);
}
}
// CardDataEditor.cs
[CustomEditor(typeof(CardData))]
public class CardDataEditor : Editor
{
private ReorderableList abilityList;
public void OnEnable()
{
abilityList = new ReorderableList(
serializedObject,
serializedObject.FindProperty("onPlayed"),
draggable: true,
displayHeader: true,
displayAddButton: true,
displayRemoveButton: true);
abilityList.onRemoveCallback = (ReorderableList l) => {
l.serializedProperty.serializedObject.Update();
var obj = l.serializedProperty.GetArrayElementAtIndex(l.index).objectReferenceValue;
DestroyImmediate(obj, true);
AssetDatabase.SaveAssets();
l.serializedProperty.DeleteArrayElementAtIndex(l.index);
l.serializedProperty.serializedObject.ApplyModifiedProperties();
};
abilityList.onAddCallback = (ReorderableList l) => {
var index = l.serializedProperty.arraySize;
l.serializedProperty.arraySize++;
l.index = index;
var element = l.serializedProperty.GetArrayElementAtIndex(index);
// Hard coding a specific ability for now
var cardData = (CardData)target;
var newAbility = ScriptableObject.CreateInstance<DrawCards>();
newAbility.name = "test";
newAbility.numCards = 22;
element.objectReferenceValue = newAbility;
AssetDatabase.AddObjectToAsset(newAbility, cardData);
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
serializedObject.ApplyModifiedProperties();
};
// Will use this to provide a menu of abilities to choose from.
/*
abilityList.onAddDropdownCallback = (Rect buttonRect, ReorderableList l) => {
var menu = new GenericMenu();
var guids = AssetDatabase.FindAssets("", new[]{"Assets/CardAbility"});
foreach (var guid in guids) {
var path = AssetDatabase.GUIDToAssetPath(guid);
menu.AddItem(new GUIContent("Mobs/" + Path.GetFileNameWithoutExtension(path)), false, clickHandler, new WaveCreationParams() {Type = MobWave.WaveType.Mobs, Path = path});
}
menu.ShowAsContext();
};
*/
// Will use this to render CardAbility properties
/*
abilityList.drawElementCallback = (Rect rect, int index, bool isActive, bool isFocused) => {
};
*/
}
public override void OnInspectorGUI()
{
serializedObject.Update();
DrawDefaultInspector();
abilityList.DoLayoutList();
// XXX: Ultimately don't expect to use these, experimenting with
// other ways of adding/deleting.
if (GUILayout.Button("Add Ability")) {
var cardData = (CardData)target;
var newAbility = ScriptableObject.CreateInstance<CardAbility>();
AssetDatabase.AddObjectToAsset(newAbility, cardData);
AssetDatabase.SaveAssets();
}
if (GUILayout.Button("Clear All Abilities")) {
var path = AssetDatabase.GetAssetPath(target);
Object[] assets = AssetDatabase.LoadAllAssetRepresentationsAtPath(path);
for (int i = 0; i < assets.Length; i++) {
if (assets[i] is CardAbility) {
Object.DestroyImmediate(assets[i], true);
}
}
AssetDatabase.SaveAssets();
}
serializedObject.ApplyModifiedProperties();
}
}
Ok I finally figured this out. I read a hundred stack overflow and forum posts trying to understand this, so I'm paying it forward, hopefully this helps someone else navigate this. This produces an Editor like the image below, where OnPlayed is an array of polymorphic ScriptableObjects. These CardAbility SO's are stored as sub-assets on the owning ScriptableObject (CardData). Still more to clean up here, and it could be made more generic, but should be a good start for someone else trying to do this.
The [+] button produces a list of all CardAbility SO's that are available to add.
And the properties for a concrete CardAbility are rendered dynamically.
One of the weirdest thing about all this is that you can't render the contents of an objectReferenceValue using PropertyField, you have to construct a SerializedObject first like this:
SerializedObject nestedObject = new SerializedObject(element.objectReferenceValue);
Thanks to Unity: Inspector can't find field of ScriptableObject for that tip.
Some other great resources for ReorderableList:
https://va.lent.in/unity-make-your-lists-functional-with-reorderablelist/
https://sites.google.com/site/tuxnots/gamming/unity3d/unitymakeyourlistsfunctionalwithreorderablelist
https://sandordaemen.nl/blog/unity-3d-extending-the-editor-part-3/
// CardData.cs
[CreateAssetMenu(fileName = "CardData", menuName = "Card Game/CardData", order = 1)]
public class CardData : ScriptableObject
{
public enum CardType
{
Attack,
Skill
}
public CardType type;
public Sprite image;
public string description;
// XXX: Hidden in inspector because it will be drawn by custom Editor.
[HideInInspector]
public CardAbility[] onPlayed;
}
// CardAbility.cs
public abstract class CardAbility : ScriptableObject
{
public abstract void Resolve();
}
// DrawCards.cs
public class DrawCards : CardAbility
{
public int numCards = 1;
public override void Resolve()
{
Deck.instance.DrawCards(numCards);
}
}
// HealPlayer.cs
public class HealPlayer : CardAbility
{
public int healAmount = 10;
public override void Resolve()
{
Player.instance.Heal(healAmount);
}
}
// CardDataEditor.cs
[CustomEditor(typeof(CardData))]
[CanEditMultipleObjects]
public class CardDataEditor : Editor
{
private ReorderableList abilityList;
private SerializedProperty onPlayedProp;
private struct AbilityCreationParams {
public string Path;
}
public void OnEnable()
{
onPlayedProp = serializedObject.FindProperty("onPlayed");
abilityList = new ReorderableList(
serializedObject,
onPlayedProp,
draggable: true,
displayHeader: true,
displayAddButton: true,
displayRemoveButton: true);
abilityList.drawHeaderCallback = (Rect rect) => {
EditorGUI.LabelField(rect, "OnPlayed Abilities");
};
abilityList.onRemoveCallback = (ReorderableList l) => {
var element = l.serializedProperty.GetArrayElementAtIndex(l.index);
var obj = element.objectReferenceValue;
AssetDatabase.RemoveObjectFromAsset(obj);
DestroyImmediate(obj, true);
l.serializedProperty.DeleteArrayElementAtIndex(l.index);
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
ReorderableList.defaultBehaviours.DoRemoveButton(l);
};
abilityList.drawElementCallback = (Rect rect, int index, bool isActive, bool isFocused) => {
SerializedProperty element = onPlayedProp.GetArrayElementAtIndex(index);
rect.y += 2;
rect.width -= 10;
rect.height = EditorGUIUtility.singleLineHeight;
if (element.objectReferenceValue == null) {
return;
}
string label = element.objectReferenceValue.name;
EditorGUI.LabelField(rect, label, EditorStyles.boldLabel);
// Convert this element's data to a SerializedObject so we can iterate
// through each SerializedProperty and render a PropertyField.
SerializedObject nestedObject = new SerializedObject(element.objectReferenceValue);
// Loop over all properties and render them
SerializedProperty prop = nestedObject.GetIterator();
float y = rect.y;
while (prop.NextVisible(true)) {
if (prop.name == "m_Script") {
continue;
}
rect.y += EditorGUIUtility.singleLineHeight;
EditorGUI.PropertyField(rect, prop);
}
nestedObject.ApplyModifiedProperties();
// Mark edits for saving
if (GUI.changed) {
EditorUtility.SetDirty(target);
}
};
abilityList.elementHeightCallback = (int index) => {
float baseProp = EditorGUI.GetPropertyHeight(
abilityList.serializedProperty.GetArrayElementAtIndex(index), true);
float additionalProps = 0;
SerializedProperty element = onPlayedProp.GetArrayElementAtIndex(index);
if (element.objectReferenceValue != null) {
SerializedObject ability = new SerializedObject(element.objectReferenceValue);
SerializedProperty prop = ability.GetIterator();
while (prop.NextVisible(true)) {
// XXX: This logic stays in sync with loop in drawElementCallback.
if (prop.name == "m_Script") {
continue;
}
additionalProps += EditorGUIUtility.singleLineHeight;
}
}
float spacingBetweenElements = EditorGUIUtility.singleLineHeight / 2;
return baseProp + spacingBetweenElements + additionalProps;
};
abilityList.onAddDropdownCallback = (Rect buttonRect, ReorderableList l) => {
var menu = new GenericMenu();
var guids = AssetDatabase.FindAssets("", new[]{"Assets/CardAbility"});
foreach (var guid in guids) {
var path = AssetDatabase.GUIDToAssetPath(guid);
var type = AssetDatabase.LoadAssetAtPath(path, typeof(UnityEngine.Object));
if (type.name == "CardAbility") {
continue;
}
menu.AddItem(
new GUIContent(Path.GetFileNameWithoutExtension(path)),
false,
addClickHandler,
new AbilityCreationParams() {Path = path});
}
menu.ShowAsContext();
};
}
private void addClickHandler(object dataObj) {
// Make room in list
var data = (AbilityCreationParams)dataObj;
var index = abilityList.serializedProperty.arraySize;
abilityList.serializedProperty.arraySize++;
abilityList.index = index;
var element = abilityList.serializedProperty.GetArrayElementAtIndex(index);
// Create the new Ability
var type = AssetDatabase.LoadAssetAtPath(data.Path, typeof(UnityEngine.Object));
var newAbility = ScriptableObject.CreateInstance(type.name);
newAbility.name = type.name;
// Add it to CardData
var cardData = (CardData)target;
AssetDatabase.AddObjectToAsset(newAbility, cardData);
AssetDatabase.SaveAssets();
element.objectReferenceValue = newAbility;
serializedObject.ApplyModifiedProperties();
}
public override void OnInspectorGUI()
{
serializedObject.Update();
DrawDefaultInspector();
abilityList.DoLayoutList();
if (GUILayout.Button("Delete All Abilities")) {
var path = AssetDatabase.GetAssetPath(target);
Object[] assets = AssetDatabase.LoadAllAssetRepresentationsAtPath(path);
for (int i = 0; i < assets.Length; i++) {
if (assets[i] is CardAbility) {
Object.DestroyImmediate(assets[i], true);
}
}
AssetDatabase.SaveAssets();
}
serializedObject.ApplyModifiedProperties();
}
}
Not sure since Editor scripting is always quite tricky if you don't have a full project in front of you.
On first glance I would say you are adding the assets to the asset but you do not remove them using AssetDatabase.RemoveObjectFromAsset
You should probably do something like
abilityList.onRemoveCallback = (ReorderableList l) =>
{
// Update should be redundant here since you already call it anyway on beginning of the draw loop
// Are you also sure the `l.index` is the correct value to use here?
var obj = l.serializedProperty.GetArrayElementAtIndex(l.index).objectReferenceValue;
AssetDatabase.RemoveObjectFromAsset(obj);
DestroyImmediate(obj, true);
l.serializedProperty.DeleteArrayElementAtIndex(l.index);
// If you do save assets also refresh
// Not sure if you should even do that though to be honest
AssetDatabase.SaveAssets();
// Also refresh here
AssetDatabase.Refresh();
// Also ApplyModifiedProperties should be redundant
};
also as commented I think you wouldn't even have to use
AssetDatabase.SaveAssets();
without it the assets would simply be marked as dirty and be saved along with the scene on the next CTRL+S press.
However, afaik, if you do it you should always combine it with AssetDatabase.Refresh(); to actually see the changes made reflected in the assets view
I need a reliable method to get the height of the text contained in a RichTextBlock, even before it is actually drawn on the scene.
Using the normal Measure() method produces a weird result, as it can be seen in the MVCE: https://github.com/cghersi/UWPExamples/tree/master/MeasureText (I want to keep fiexed the width, and measure the final height, but the result of DesiredSize is far different from the actual height!!).
For this reason, I found a rough method (mentioned here https://stackoverflow.com/a/45937298/919700), that I extended to serve my purpose, where we use some Win2D API to compute the content height.
The problem is that in some cases, this method provides an height that is smaller than the expected one.
Is there a general way to retrieve the (correct) height of a
TextBlock, even before it is drawn on the scene?
If this is not the case, what am I doing wrong?
Here's my code (which you can find also as MVCE here: https://github.com/cghersi/UWPExamples/tree/master/RichText):
public sealed partial class MainPage
{
public static readonly FontFamily FONT_FAMILY = new FontFamily("Assets/paltn.ttf#Palatino-Roman");
public const int FONT_SIZE = 10;
private readonly Dictionary<string, object> FONT = new Dictionary<string, object>
{
{ AttrString.FONT_FAMILY_KEY, FONT_FAMILY },
{ AttrString.FONT_SIZE_KEY, FONT_SIZE },
{ AttrString.LINE_HEAD_INDENT_KEY, 10 },
{ AttrString.LINE_SPACING_KEY, 1.08 },
{ AttrString.FOREGROUND_COLOR_KEY, new SolidColorBrush(Colors.Black) }
};
// ReSharper disable once PrivateFieldCanBeConvertedToLocalVariable
private readonly RichTextBlock m_displayedText;
public MainPage()
{
InitializeComponent();
// create the text block:
m_displayedText = new RichTextBlock
{
MaxLines = 0, //Let it use as many lines as it wants
TextWrapping = TextWrapping.Wrap,
AllowFocusOnInteraction = false,
IsHitTestVisible = false,
Width = 80,
Height = 30,
Margin = new Thickness(100)
};
// set the content with the right properties:
AttrString content = new AttrString("Excerpt1 InkLink", FONT);
SetRichText(m_displayedText, content);
// add to the main panel:
MainPanel.Children.Add(m_displayedText);
// compute the text height: (this gives the wrong answer!!):
double textH = GetRichTextHeight(content, (float)m_displayedText.Width);
Console.WriteLine("text height: {0}", textH);
}
public static double GetRichTextHeight(AttrString text, float maxWidth)
{
if (text == null)
return 0;
CanvasDevice device = CanvasDevice.GetSharedDevice();
double finalH = 0;
foreach (AttributedToken textToken in text.Tokens)
{
CanvasTextFormat frmt = new CanvasTextFormat()
{
Direction = CanvasTextDirection.LeftToRightThenTopToBottom,
FontFamily = textToken.Get(AttrString.FONT_FAMILY_KEY, FONT_FAMILY).Source,
FontSize = textToken.Get(AttrString.FONT_SIZE_KEY, FONT_SIZE),
WordWrapping = CanvasWordWrapping.Wrap
};
CanvasTextLayout layout = new CanvasTextLayout(device, textToken.Text, frmt, maxWidth, 0f);
finalH += layout.LayoutBounds.Height;
}
return finalH;
//return textBlock.Blocks.Sum(block => block.LineHeight);
}
private static void SetRichText(RichTextBlock label, AttrString str)
{
if ((str == null) || (label == null))
return;
label.Blocks.Clear();
foreach (AttributedToken token in str.Tokens)
{
Paragraph paragraph = new Paragraph()
{
TextAlignment = token.Get(AttrString.TEXT_ALIGN_KEY, TextAlignment.Left),
TextIndent = token.Get(AttrString.LINE_HEAD_INDENT_KEY, 0),
};
double fontSize = token.Get(AttrString.FONT_SIZE_KEY, FONT_SIZE);
double lineSpacing = token.Get(AttrString.LINE_SPACING_KEY, 1.0);
paragraph.LineHeight = fontSize * lineSpacing;
paragraph.LineStackingStrategy = LineStackingStrategy.BlockLineHeight;
Run run = new Run
{
Text = token.Text,
FontFamily = token.Get(AttrString.FONT_FAMILY_KEY, FONT_FAMILY),
FontSize = fontSize,
Foreground = token.Get(AttrString.FOREGROUND_COLOR_KEY, new SolidColorBrush(Colors.Black)),
FontStyle = token.Get(AttrString.ITALIC_KEY, false) ?
Windows.UI.Text.FontStyle.Italic : Windows.UI.Text.FontStyle.Normal
};
paragraph.Inlines.Add(run);
label.Blocks.Add(paragraph);
}
}
}
public class AttrString
{
public const string FONT_FAMILY_KEY = "Fam";
public const string FONT_SIZE_KEY = "Size";
public const string LINE_HEAD_INDENT_KEY = "LhI";
public const string LINE_SPACING_KEY = "LSpace";
public const string FOREGROUND_COLOR_KEY = "Color";
public const string ITALIC_KEY = "Ita";
public const string TEXT_ALIGN_KEY = "Align";
public const string LINE_BREAK_MODE_KEY = "LineBreak";
public static Dictionary<string, object> DefaultCitationFont { get; set; }
public static Dictionary<string, object> DefaultFont { get; set; }
public List<AttributedToken> Tokens { get; set; }
public AttrString(string text, Dictionary<string, object> attributes)
{
Tokens = new List<AttributedToken>();
Append(text, attributes);
}
public AttrString(AttrString copy)
{
if (copy?.Tokens == null)
return;
Tokens = new List<AttributedToken>(copy.Tokens);
}
public AttrString Append(string text, Dictionary<string, object> attributes)
{
Tokens.Add(new AttributedToken(text, attributes));
return this;
}
public bool IsEmpty()
{
foreach (AttributedToken t in Tokens)
{
if (!string.IsNullOrEmpty(t.Text))
return false;
}
return true;
}
public override string ToString()
{
StringBuilder sb = new StringBuilder();
foreach (AttributedToken t in Tokens)
{
sb.Append(t.Text);
}
return sb.ToString();
}
}
public class AttributedToken
{
public string Text { get; set; }
public Dictionary<string, object> Attributes { get; set; }
public AttributedToken(string text, Dictionary<string, object> attributes)
{
Text = text;
Attributes = attributes;
}
public T Get<T>(string key, T defaultValue)
{
if (string.IsNullOrEmpty(key) || (Attributes == null))
return defaultValue;
if (Attributes.ContainsKey(key))
return (T)Attributes[key];
else
return defaultValue;
}
public override string ToString()
{
return Text;
}
}
** UPDATE **:
After further digging into the issue, the problem seems related to the lack of configurability for the CanvasTextFormat object, especially for the indentation of the first line (expressed in the RichTextBlock using the property Paragraph.TextIndent). Is there any way to specify such setting in a CanvasTextFormat object?
Looking at your MeasureText MVCE code, the problem with calling Measure() on the RichTextBlock comes down to this line:
m_textBlock.Margin = new Thickness(200);
This sets a universal margin of 200 on all sides, which means the element needs at least 200 width on the left plus 200 width on the right, or 400 width. Since your Measure(300,infinite) specifies an available width of less than the minimum required 400 width, the RichTextBlock decides that the best it can do is wrap the text at every character, producing the massive 5740 pixel height (plus the 200+200 height from the margin).
If you remove that line, the RichTextBlock will use the specified constraint of 300 and correctly measure its desired height as 90 pixels, which is what it renders as on screen (if you set Width=300 or otherwise result in the actual element layout to have the same constraint).
Alternatively, since you know the width you want for the element, you could set Width=300 on it and it will then measure with that width. The Height will be expanded as a result of the set Margin, though.
I'm assuming you don't actually have Margin=200 set in your real app, and instead have something smaller like Margin=5 to account for margin you actually want when the RichTextBlock is in the tree and drawing. If this is the case, then you can either:
Use the Width=300 approach for measuring and subtract off the top+bottom margin from the DesireSize.Height.
Measure with (300+margin.Left+margin.Right) as the width so that once the margin is subtracted off from that total availableSize the remaining width the text can use is your intended 300. You'll still need to subtract off the top+bottom margin from the DesireSize.Height.
I have a Xamarin.Forms app that displays a ViewFlipper (https://github.com/TorbenK/ViewFlipper) inside a CarouselView.
I would like the ViewFlipper to flip back to the front when changing pages inside the carousel. But I can't seem to figure out how to access the ViewFlipper.
I have the following working code:
public class CarouselContent
{
public string FrontImg { get; set; }
public string BackImg { get; set; }
}
public class MainPage : ContentPage
{
public MainPage()
{
var pages = new ObservableCollection<CarouselContent>();
var page1 = new CarouselContent();
page1.FrontImg = "page1Front";
page1.BackImg = "page1Back";
var page2 = new CarouselContent();
page2.FrontImg = "page2Front";
page2.BackImg = "page2Back";
pages.Add(page1);
pages.Add(page2);
var carouselView = new Carousel(pages);
Content = carouselView;
}
}
public class Carousel : AbsoluteLayout
{
private DotButtonsLayout dotLayout;
private CarouselView carousel;
public Carousel(ObservableCollection<CarouselContent> pages)
{
carousel = new CarouselView();
var template = new DataTemplate(() =>
{
//create page
var absLayout = new AbsoluteLayout();
//create images for the flipper
var frontImg = new Image
{
Aspect = Aspect.AspectFit
};
frontImg.SetBinding(Image.SourceProperty, "FrontImg");
var backImg = new Image
{
Aspect = Aspect.AspectFit
};
backImg.SetBinding(Image.SourceProperty, "BackImg");
//create flipper
var flipper = new ViewFlipper.FormsPlugin.Abstractions.ViewFlipper();
flipper.FrontView = frontImg;
flipper.BackView = backImg;
//Add flipper to page
absLayout.Children.Add(flipper);
return absLayout;
});
carousel.ItemsSource = pages;
carousel.ItemTemplate = template;
Children.Add(carousel);
}
}
I tried adding the ViewFlipper to the CarouselContent but I couldn't get that to work. Any ideas?
EDIT:
I tried creating an AbsoluteLayout with bindable items and bind the items created in CarouselContent in the datatemplate of the CarouselView, but the line '(b as BindableAbsLayout).Children.Add((View)v);' in BindableAbsLayout is never called. What am I doing wrong?
class BindableAbsLayout : AbsoluteLayout
{
public static readonly BindableProperty ItemsProperty =
BindableProperty.Create(nameof(Items), typeof(ObservableCollection<View>), typeof(BindableAbsLayout), null,
propertyChanged: (b, o, n) =>
{
(n as ObservableCollection<View>).CollectionChanged += (coll, arg) =>
{
switch (arg.Action)
{
case NotifyCollectionChangedAction.Add:
foreach (var v in arg.NewItems)
(b as BindableAbsLayout).Children.Add((View)v);
break;
case NotifyCollectionChangedAction.Remove:
foreach (var v in arg.NewItems)
(b as BindableAbsLayout).Children.Remove((View)v);
break;
}
};
});
public ObservableCollection<View> Items
{
get { return (ObservableCollection<View>)GetValue(ItemsProperty); }
set { SetValue(ItemsProperty, value); }
}
}
public class CarouselContent
{
private ViewFlipper.FormsPlugin.Abstractions.ViewFlipper _flipper;
private ObservableCollection<View> _items;
public ObservableCollection<View> Items
{
get { return _items; }
}
public CarouselContent(string frontImgStr, string backImgStr)
{
_items = new ObservableCollection<View>();
_flipper = new ViewFlipper.FormsPlugin.Abstractions.ViewFlipper();
var frontImg = new Image
{
Aspect = Aspect.AspectFit
};
frontImg.Source = frontImgStr;
var backImg = new Image
{
Aspect = Aspect.AspectFit
};
backImg.Source = backImgStr;
_flipper.FrontView = frontImg;
_flipper.BackView = backImg;
AbsoluteLayout.SetLayoutBounds(_flipper, new Rectangle(0.5, 0.05, 0.85, 0.85));
AbsoluteLayout.SetLayoutFlags(_flipper, AbsoluteLayoutFlags.All);
Items.Add(_flipper);
}
}
public class Carousel : AbsoluteLayout
{
private DotButtonsLayout dotLayout;
private CarouselView carousel;
public Carousel(ObservableCollection<CarouselContent> pages)
{
carousel = new CarouselView();
var template = new DataTemplate(() =>
{
var absLayout = new BindableAbsLayout();
absLayout.BackgroundColor = Color.FromHex("#68BDE4");
absLayout.SetBinding(BindableAbsLayout.ItemsProperty,"Items");
return absLayout;
});
carousel.ItemsSource = pages;
carousel.ItemTemplate = template;
Children.Add(carousel);
}
}
Not sure what the best practice is here, but you could try accessing it via the ItemSelected Event (which fires every time you change back and forth in the carouselview)
Wire it up
carousel.ItemSelected += carouselOnItemSelected;
Get your ViewFlipper
private void carouselOnItemSelected(object sender, SelectedItemChangedEventArgs selectedItemChangedEventArgs)
{
CarouselContent carouselContent = selectedItemChangedEventArgs.SelectedItem;
ViewFlipper viewFlipper = carouselContent.Children[0];
viewFlipper.FlipState = ViewFlipper.FrontView;
}
I was profiling a WPF application that uses Catel as MVVM Framework and I've seen that in a ViewModel I've got 2 retention path as
Now I create such RadMenuItem in a behavior I attach to the context menu and they're defined as
protected virtual IEnumerable<RadMenuItem> GetRowMenuItems(RadContextMenu contextMenu)
{
var rowItems = new List<RadMenuItem>();
RadMenuItem saveSettings = new RadMenuItem
{
Tag = "force",
Header = CoreResources.LBL_SAVE_SETTINGS,
Command = DefaultRtViewContextMenuCommands.SaveLayoutDataCommand,
CommandParameter = AssociatedObject,
Icon = new Image { Source = new BitmapImage(new Uri("pack://application:,,,/IF.Tesoreria.Client.WPF.Core;component/Media/save.png")) }
};
rowItems.Add(saveSettings);
RadMenuItem loadSettings = new RadMenuItem
{
Tag = "force",
Header = CoreResources.LBL_LOAD_SETTINGS,
Command = DefaultRtViewContextMenuCommands.LoadLayoutDataCommand,
CommandParameter = AssociatedObject,
Icon = new Image { Source = new BitmapImage(new Uri("pack://application:,,,/IF.Tesoreria.Client.WPF.Core;component/Media/load.png")) }
};
Now The commands are defined in
public class DefaultRtViewContextMenuCommands
{
public static Command<FlexGridCommandParameter> SaveLayoutDataCommand { get; private set; }
public static Command<FlexGridCommandParameter> LoadLayoutDataCommand { get; private set; }
private static void OnLoadLayoutCommandExecute(FlexGridCommandParameter grid)
{
Argument.IsNotNull(() => grid);
var dependencyResolver = DependencyResolverManager.Default;
var openFileService = dependencyResolver.DefaultDependencyResolver.Resolve<IOpenFileService>();
openFileService.Filter = "Gridview setting file|*.flexgrid";
if (openFileService.DetermineFile())
{
// User selected a file
using (var fs = File.OpenRead(openFileService.FileName))
{
GridViewPersistenceHelper.LoadLayout(grid.Grid, fs, null);
}
}
}
private static void OnSaveLayoutCommandExecute(FlexGridCommandParameter grid)
{
Argument.IsNotNull(() => grid);
var dependencyResolver = DependencyResolverManager.Default;
var saveFileService = dependencyResolver.DefaultDependencyResolver.Resolve<ISaveFileService>();
saveFileService.Filter = "Gridview setting file|*.flexgrid";
if (saveFileService.DetermineFile())
{
// User selected a file
using (var fs = File.OpenWrite(saveFileService.FileName))
{
GridViewPersistenceHelper.SaveLayout(grid.Grid, fs);
}
}
}
static DefaultRtViewContextMenuCommands()
{
viewModelFactory = ServiceLocator.Default.ResolveType<IViewModelFactory>();
portfolioService = ServiceLocator.Default.ResolveType<IPortfoliosService>();
pkInstrumentsService = ServiceLocator.Default.ResolveType<IPkInstrumentsService>();
SaveLayoutDataCommand = new Command<FlexGridCommandParameter>(OnSaveLayoutCommandExecute,_=>true);
LoadLayoutDataCommand = new Command<FlexGridCommandParameter>(OnLoadLayoutCommandExecute,_=>true);
}
What am I doing wrong?
Thanks
radMenuItem.Command = null;
Works for me. You can decompile and see that when you do it, the menu item unregisters himself from the Command’s CanExecuteChanged
I'm trying to set underline on UILabel of MvxTableViewCell.
[Register("KittenCell")]
public class KittenCell : MvxTableViewCell
{
private UIImageView MainImage;
private UILabel NameLabel;
private UILabel PriceValueLabel;
private UILabel PriceLabel;
public static readonly NSString Key = new NSString ("KittenCell");
private MvxImageViewLoader imageLoader;
public KittenCell ()
{
CreateLayout ();
InitializeBindings ();
}
public KittenCell(IntPtr handle)
: base(handle)
{
CreateLayout ();
InitializeBindings ();
}
void CreateLayout ()
{
MainImage = new UIImageView (new RectangleF (0, 0, 160, 100));
NameLabel = new UILabel (new RectangleF (168, 15, 144, 21));
NameLabel.TextAlignment = UITextAlignment.Left;
var attrs = new UIStringAttributes {
UnderlineStyle = NSUnderlineStyle.Single
};
NameLabel.AttributedText =new NSAttributedString(NameLabel.Text,attrs);
PriceLabel = new UILabel (new RectangleF (168, 59, 57, 21));
PriceLabel.Text = "Price:";
PriceLabel.TextAlignment = UITextAlignment.Left;
PriceValueLabel = new UILabel (new RectangleF (228, 59, 84, 21));
PriceValueLabel.TextAlignment = UITextAlignment.Left;
PriceValueLabel.TextColor = UIColor.Blue;
ContentView.AddSubviews (MainImage, NameLabel, PriceLabel, PriceValueLabel);
}
void InitializeBindings ()
{
imageLoader = new MvxImageViewLoader (() => this.MainImage);
this.DelayBind (() => {
var set = this.CreateBindingSet<KittenCell, Kitten> ();
set.Bind (NameLabel).To(kitten => kitten.Name);
set.Bind (PriceValueLabel).To(kitten => kitten.Price);
set.Bind (imageLoader).To(kitten => kitten.ImageUrl);
set.Apply ();
});
}
}
but when UILabel is shown there is no underline under it. My explanation is that underline is applied on text that is not already binded. But how is possible to set it after binding?
You could use a converter something along this:
public class UnderlineTextValueConverter : MvxValueConverter<string, NSAttributedString>
{
protected override NSAttributedString Convert(string value, Type targetType, object parameter, CultureInfo cultureInfo)
{
var attrs = new UIStringAttributes {
UnderlineStyle = NSUnderlineStyle.Single
};
return new NSAttributedString(value, attrs);
}
}
and then update your binding to this...
set.Bind (NameLabel).For(l => l.AttributedText).To(kitten => kitten.Name).WithConversion("UnderlineText", null);
The above code is untested, but it should work with no or little modification.