I made a custom render shadow for the frame element - everything is fine the shadow that turns out to suit me, but because of using this custom render I get a bad effect on the RadioButton element, how do I edit this render so that the radio button element is normal?
sample image
/// <summary>
/// Renderer to update all frames with better shadows matching material design standards
/// </summary>
public class MaterialFrameRenderer : FrameRenderer
{
public override void Draw(CGRect rect)
{
base.Draw(rect);
// Update shadow to match better material design standards of elevation
Layer.ShadowRadius = Element.CornerRadius;
Layer.ShadowColor = UIColor.Gray.CGColor;
Layer.ShadowOffset = new CGSize(2, 2);
Layer.ShadowOpacity = 0.40f;
Layer.ShadowPath = UIBezierPath.FromRect(Layer.Bounds).CGPath;
Layer.MasksToBounds = false;
}
}
It seems that something goes wrong with RadioButton Rendering , we even can't create custom renderer for RadioButton on iOS ,check https://forums.xamarin.com/discussion/comment/430069#Comment_430069 .
I strongly recommend you to use Xamarin.Forms.InputKit instead of Xamarin RadioButton .
It is pretty nice.
xmlns:input="clr-namespace:Plugin.InputKit.Shared.Controls;assembly=Plugin.InputKit"
<Frame Margin="100" CornerRadius="20" >
<input:RadioButtonGroupView>
<input:RadioButton Text="Option 1" />
<input:RadioButton Text="Option 2" />
<input:RadioButton Text="Option 3" />
<input:RadioButton Text="Option 4" />
</input:RadioButtonGroupView>
</Frame>
</ContentPage.Content>
Related
I have a simple grid layout:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="5*" />
<RowDefinition Height="1*" />
</Grid.RowDefinitions>
<Button Grid.Row="0"
Text="Test1"
VerticalOptions="Fill"
BackgroundColor="Green"/>
<Button Grid.Row="1"
Text="Test2"
VerticalOptions="Fill"
BackgroundColor="Red"/>
</Grid>
I expect the red area to extend all the way to the bottom of the screen. However, There's some white space.
If I change it to a collection view with a footer, it properly fills the bottom section of the screen:
For reference:
<CollectionView>
<CollectionView.Header>
<Button
Text="Test1"
VerticalOptions="Fill"
BackgroundColor="Green"/>
</CollectionView.Header>
<CollectionView.EmptyView>
<Label Text="No items"/>
</CollectionView.EmptyView>
<CollectionView.Footer>
<Button
Text="Test2"
VerticalOptions="Fill"
BackgroundColor="Red"/>
</CollectionView.Footer>
</CollectionView>
Is there a correct way to fill that bottom "footer" area on a grid view? or without using a collection view?
I don't think it matters but I'm running in iOS simulator on Mac iOS 16.2.
The white space at the bottom of the screen is iOS Page UseSafeArea in iOS. However, it is not yet implemented in Maui and you can track the MAUI issue here: https://github.com/dotnet/maui/issues/5856.
As an alternative workaround for now, you can set the Page Padding value to remove the bottom white space. In the OnAppearing Method, set the safeInsets of the page:
protected override void OnAppearing()
{
base.OnAppearing();
DeviceSafeInsetsService d = new DeviceSafeInsetsService();
double topArea = d.GetSafeAreaTop();
double bottomArea = d.GetSafeAreaBottom();
var safeInsets = On<iOS>().SafeAreaInsets();
safeInsets.Top = -topArea;
safeInsets.Bottom = -bottomArea;
Padding = safeInsets;
}
And then write platform specific code to get the topArea and bottomArea value.
1.Create DeviceSafeInsetsService.cs in Project folder.
public partial class DeviceSafeInsetsService
{
public partial double GetSafeAreaTop();
public partial double GetSafeAreaBottom();
}
2.And then under the Project/Platform/iOS folder, create a iOS specific class DeviceSafeInsetsService.cs:
public partial class DeviceSafeInsetsService
{
public partial double GetSafeAreaBottom()
{
if (UIDevice.CurrentDevice.CheckSystemVersion(11, 0))
{
UIWindow window = UIApplication.SharedApplication.Delegate.GetWindow();
var bottomPadding = window.SafeAreaInsets.Bottom;
return bottomPadding;
}
return 0;
}
public partial double GetSafeAreaTop()
{
if (UIDevice.CurrentDevice.CheckSystemVersion(11, 0))
{
UIWindow window = UIApplication.SharedApplication.Delegate.GetWindow();
var TopPadding = window.SafeAreaInsets.Top;
return TopPadding;
}
return 0;
}
}
For more details, you can refer to this sample .NET MAUI Platform Code Sample.
Set your Corner Radius property to 0 in your button control's property definition
<Button
CornerRadius="0"
Text="Test2"
VerticalOptions="Fill"
BackgroundColor="Red"/>
In Xamarin.Forms, an Effect can be attached to a View. In my case, the View is displaying an Image. And the effect is making a colored "glow" around the visible pixels of the image. XAML:
<Image Source="{Binding LogoImage}" ...>
<Image.Effects>
<effects:GlowEffect Radius="5" Color="White" />
</Image.Effects>
</Image>
The effect is implemented as a subclass of RoutingEffect:
public class GlowEffect : Xamarin.Forms.RoutingEffect
{
public GlowEffect() : base("Core.ImageGlowEffect")
{
}
...
}
On each platform, there is a PlatformEffect to implement the effect. For iOS:
using ...
[assembly: ExportEffect(typeof(Core.Effects.ImageGlowEffect), "ImageGlowEffect")]
namespace Core.Effects
{
public class ImageGlowEffect : PlatformEffect
{
protected override void OnAttached()
{
ApplyGlow();
}
protected override void OnElementPropertyChanged( PropertyChangedEventArgs e )
{
base.OnElementPropertyChanged( e );
if (e.PropertyName == "Source") {
ApplyGlow();
}
}
private void ApplyGlow()
{
var imageView = Control as UIImageView;
if (imageView.Image == null)
return;
var effect = (GlowEffect)Element.Effects.FirstOrDefault(e => e is GlowEffect);
if (effect != null) {
CGRect outSize = AVFoundation.AVUtilities.WithAspectRatio( new CGRect( new CGPoint(), imageView.Image.Size ), imageView.Frame.Size );
...
}
}
...
}
}
The above code works if Source is changed dynamically: at the time Source changes, the UIImageView (Bounds or Frame) has a size. BUT if the Source is set statically in XAML, that logic does not run: so the only call to ApplyGlow is during OnAttach. UNFORTUNATELY, during OnAttach, the UIImageView has size (0, 0).
How get this effect to work on iOS, with a static Source?
NOTE: The equivalent Android effect works via a handler attached to ImageView.ViewTreeObserver.PreDraw - at which time the size is known. So if there is an iOS equivalent event, that would be one way to solve.
More Details:
The original implementation used the original image size (imageView.Image.Size) - which is available during OnAttach. This can be made to "work", but is not satisfactory: the glow is applied to the full size image. If the image is significantly larger than the view area, the glow becomes much too small a radius (iOS shrinks the image+glow as it renders): it does not have the desired appearance.
ApplyGlow has an option to apply a tint color to the image. That tint color is different than the glow color. I mention this because it restricts the possible solutions: AFAIK, can't just set options on an image and let iOS figure out how to render it - need to explicitly resize the image and draw the resized tinted image on top of a blurred version (the glow). This code all works - if imageView.Bounds.Size (or imageView.Frame.Size) is available (and non-zero).
With a breakpoint in OnElementPropertyChanged, I've checked to see if imageView size is known for any property that is always set. No; if no properties are dynamically set, the property changes all occur before imageView has a size.
Maybe it's a workaround and I don't know if it is acceptable to you.
Add a little delay before calling ApplyGlow(); in OnAttached. After the delay, you will get the imageView.Frame.Size or imageView.Bounds.Size.
protected override async void OnAttached()
{
await Task.Delay(TimeSpan.FromSeconds(0.3));// it can be 0.2s,0.1s, depending on you
ApplyGlow();
}
And if you have set WidthRequest, HeightRequest, you can get there without the delay:
private void ApplyGlow()
{
var imageView = Control as UIImageView;
if (imageView.Image == null)
return;
Console.WriteLine(imageView.Image.Size.Width);
Console.WriteLine(imageView.Image.Size.Height);
CoreGraphics.CGSize rect = imageView.Bounds.Size;
CoreGraphics.CGSize rect2 = imageView.Frame.Size;
Console.WriteLine(rect);
Console.WriteLine(rect2);
double width = (double)Element.GetValue(VisualElement.WidthRequestProperty);
double height = (double)Element.GetValue(VisualElement.HeightRequestProperty);
Console.WriteLine(width);
Console.WriteLine(height);
double width2 = (double)Element.GetValue(VisualElement.WidthProperty);
double height2 = (double)Element.GetValue(VisualElement.HeightProperty);
Console.WriteLine(width2);
Console.WriteLine(height2);
}
I've tried the Xamarin effects mentioned in this article:
https://smstuebe.de/2016/08/29/underlinedlabel.xamarin.forms/
(Which derived from this stackoverflow question).
It works perfectly fine.
However, when I instead try to apply the label effect "AdjustsFontSizeToFitWidth", it just doesn't work.
For testing this, I've set the font size in the same sample code to 300, and I changed these lines
var label = (UILabel)Control;
var text = (NSMutableAttributedString)label.AttributedText;
var range = new NSRange(0, text.Length);
to these lines:
var label = (UILabel)Control;
var text = (NSMutableAttributedString)label.AttributedText;
var range = new NSRange(0, text.Length);
label.Lines = 1;
label.MinimumFontSize = 6;
label.AdjustsFontSizeToFitWidth = true;
I've tested it with iPhone iPhone 6 iOS 11.1 and iPhone X iOS 11.1 in the simulator.
Is there something special about this property which makes it un-usable for Xamarin effects or is it possible that this effect works only on a real device?
You need to make sure that you are setting a line truncation on the Label otherwise the normal iOS LabelRenderer is going to set the Lines property to 0 (i.e. unlimited multi-line) and thus there is nothing to resize as the entire control to sized to the text size. Also you can not set the Lines via an Effect as the routing effect happens before the renderer sets its properties and it gets reset to 0;
Set the LineBreakMode property:
<Label Text="Welcome to SizeToFitEffects, SizeToFitEffects, SizeToFitEffects, SizeToFitEffects"
LineBreakMode="TailTruncation">
<Label.Effects>
<sushi:LabelSizeToFitEffect />
</Label.Effects>
</Label>
In the effect, it is wise to set the MinimumScaleFactor to something reasonable otherwise you can end up with a font so small it is unreadable.
public class LabelSizeToFitEffect : PlatformEffect
{
protected override void OnAttached()
{
SetSizeToFit(true);
}
protected override void OnDetached()
{
SetSizeToFit(false);
}
void SetSizeToFit(bool sizeToFit)
{
var label = Control as UILabel;
label.AdjustsFontSizeToFitWidth = sizeToFit;
label.MinimumScaleFactor = 0.33f;
}
}
I have a GUI with some TextArea elements to display information. As the GUI shall react to keyevents I added a EventHandler to the scene element. Because I didn't want to add one EventHandler to each textarea I disabled them to prevent that they are focused because then the scene's eventhandler doesn't work anymore. Now I have the problem the text is displayed in gray and not in black anymore eventhough I changed that in the css file. Do you have any ideas why the text isn't black and how i could fix that?
Here is some code:
private val scene =
new Scene {
stylesheets.add("css/style.css")
onKeyTyped = (new EventHandler[KeyEvent] {
def handle(event: KeyEvent) {
...
}
})
...
}
private val description = new TextArea{
text = "some text"
wrapText = true
disable = true
styleClass.add("txtarea")
maxHeight = 400
}
.txtarea:disabled{
-fx-font-size: 18pt;
-fx-text-fill: #000000;
-fx-prompt-text-fill: #000000;
-fx-opacity: 1.0;
-fx-background-color: white;
}
You also need to provide style for the scroll pane embedded in the TextArea:
.txtarea .scroll-pane:disabled{
-fx-opacity: 1.0;
}
How do you create a JavaFX application that (instantly) dynamically resizes? Right now I have coded a simple application that dynamically resizes but the layout changes don't display until after the release of the mouse button on a drag. I want instantly see the results/layout changes before this button release.
I'm assuming this is done by just binding the correct values/controls with inverse... Any help would be great!
EDIT:
Here's how I got things working (thanks to Praeus). Exactly as he said, I had to bind my top level container/layout width and height to scene.width and scene.height. --
var scene: Scene;
Stage {
title: "Application title"
scene: scene = Scene {
content: [
XMigLayout {
width: bind scene.width;
height: bind scene.height;
...}]}}
Bind is different in JavaFX's 2.0 release. Angela Caicedo's video demonstrates how to bind properties nicely. A variable must be setup as an ObservableNumberValue and its get and set methods used. After it's setup you can then bind it to a component's property.
DoubleProperty x = new SimpleDoubleProperty(0);
x.set(1);
x.getValue();
imageView.xProperty().bind(x);
anchorPane.heightProperty().add(x);
Eric Bruno's post is another way of doing it.
scene.widthProperty().addListener(
new ChangeListener() {
public void changed(ObservableValue observable,
Object oldValue, Object newValue) {
Double width = (Double)newValue;
tbl.setPrefWidth(width);
}
});
scene.heightProperty().addListener(
new ChangeListener() {
public void changed(ObservableValue observable,
Object oldValue, Object newValue) {
Double height = (Double)newValue;
tbl.setPrefHeight(height);
}
});
Edits: Added another, more fitting answer along with where I found it.
Actually if you've already organized everything into layouts, then you might be able to just bind the top level layout container(s) width and height to the scene.width and scene.height. I don't think top level layout controls are managed by anything, so you may be safe just setting width and height of the layout control directly. If that doesn't work then you might have success binding the layoutInfo width and height.
Ideally you want to use layout management before resorting to binds. Excessive binds can be a performance problem, and can lead to slow redraws as you are resizing. In this case you can probably just use binds for the top level container, and then all of the children of the top level container should resize accordingly. At least I think that should work.
Yes I think you're on the right lines - binding would be the obvious way to do what you trying to do. You could also look at triggers.
Read the 1.3 guide to layouts as a starting point if you haven't already
The layout containers automatically manage the dynamic layout behavior of their content nodes. However, it is often desirable to also use the binding mechanism of the JavaFX Script to establish certain dynamic aspects of layout. For example, you can bind the width and height of an HBox container to the Scene height and width so that whenever Scene resizes, the HBox container resizes as well.
It's also probably worth reading articles and blog posts by JavaFX layout guru Amy Fowler
Here's what I ended up doing to get my resizing how I liked. There has to be an easier way but I surely don't know it.
My main method loads my controller and scene then calls my controllers init() method
FXMLLoader loader = new FXMLLoader(getClass().getResource("sample.fxml"));
Parent root = (Parent) loader.load();
primaryStage.setTitle("Robot Interface");
primaryStage.setScene(new Scene(root));
primaryStage.show();
Controller controller = (Controller)loader.getController();
controller.init();
I am using a gridpane that dynamically resizes as the whole stage is resized by the user so it always fits. This is becaus its percentage sizing. This is done in the fxml way easier than with binding IMO.
<GridPane fx:id="gridPane" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="500.0" prefWidth="700.0" xmlns="http://javafx.com/javafx/8.0.121" xmlns:fx="http://javafx.com/fxml/1" fx:controller="sample.Controller">
<columnConstraints>
<ColumnConstraints percentWidth="50" />
<ColumnConstraints percentWidth="50" />
</columnConstraints>
<rowConstraints>
<RowConstraints percentHeight="35" />
<RowConstraints percentHeight="35" />
<RowConstraints percentHeight="35" />
</rowConstraints>
<children>
<TextArea fx:id="textArea_Console" prefHeight="200.0" prefWidth="200.0" promptText="console/reponse" GridPane.columnIndex="1" GridPane.rowIndex="2" />
<!-- <MediaView fx:id="mediaView_Images" fitHeight="225.0" fitWidth="225.0" GridPane.halignment="LEFT" GridPane.valignment="BOTTOM" /> -->
<TextArea fx:id="textArea_RoboAvail" maxHeight="-Infinity" maxWidth="-Infinity" prefHeight="121.0" prefWidth="123.0" promptText="robots available" GridPane.columnIndex="1" GridPane.halignment="RIGHT" GridPane.valignment="TOP" />
<TextArea fx:id="textArea_SensorData" maxHeight="-Infinity" maxWidth="-Infinity" prefHeight="249.0" prefWidth="106.0" promptText="sensor data" GridPane.halignment="RIGHT" GridPane.valignment="TOP" />
<TextArea fx:id="textArea_Scripting" prefHeight="200.0" prefWidth="200.0" promptText="Scripting" GridPane.columnIndex="1" GridPane.rowIndex="1" />
<Label text="Currently Viewing" GridPane.halignment="LEFT" GridPane.valignment="TOP" />
<TextArea maxHeight="-Infinity" maxWidth="-Infinity" prefHeight="250.0" prefWidth="100.0" promptText="to do list" GridPane.columnIndex="1" GridPane.halignment="LEFT" GridPane.valignment="TOP" />
<ImageView fx:id="imageView_Images" fitHeight="233.0" fitWidth="225.0" pickOnBounds="true" preserveRatio="true" GridPane.halignment="LEFT" GridPane.valignment="TOP" />
<Label fx:id="label_CheatSheet" text="Label" GridPane.halignment="LEFT" GridPane.rowIndex="2" GridPane.valignment="BOTTOM" />
</children>
Remember how my controller class had its init() method called. Well init() calls setupUI where I get my height and width and create callbacks for height and width changes later. Those change my class wide variables mHeight and mWidth;
private void setupUI()
{
imageView_Images.setImage( new Image("file:///C:\\Users\\administration\\IdeaProjects\\Robot User Interface\\Media\\filler.jpeg"));
mHeight = gridPane.getHeight();
mWidth = gridPane.getWidth();
//for initial startup sizing
dynamicRsize();
//callbacks to detect changes for sizing
gridPane.widthProperty().addListener(new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
mWidth = newValue.doubleValue();
dynamicRsize();
}
});
gridPane.heightProperty().addListener(new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
mHeight = newValue.doubleValue();
dynamicRsize();
}
});
setup_TextArea_Scriptng();
setup_Label_CheatSheet();
}
Finally I can call dynamicResize() which adjust every object in the gridpane as a percentage of that rows width or height
private void dynamicRsize()
{
//two columns 50/50
//three rows 35/35/30
double columnType1 = mWidth/2;
double rowType1 = mHeight * .35;
double rowType2 = mHeight * .3;
//node 1
imageView_Images.setFitWidth(columnType1 * .75);
textArea_SensorData.setPrefWidth(columnType1 * .25);
//node 2
//node 3
//node 4
//node 5
//node 6
}