I am trying to understand why a ScaleTransform (testing under Windows Phone 8.0) is rendering using the control's parent dimensions, instead of the control itself. I have set up a demo app that shows this behaviour, as shown below.
The dimensions and hierarchy has a reason to be, that's why I manually set the Width and Height, as it's very close to the real app.
What I'd expect is that child1 (the yellow one), which is 768x1228 and has a scale of 0.625, would render itself as if it was 480x768, but what happens is that it renders like it was 300x480 (e.g., it is rendered at 0.625% of 480, instead of 768).
Any clues?
public partial class MainPage : PhoneApplicationPage
{
private Grid root;
public MainPage()
{
InitializeComponent();
root = new Grid() {
Width = 480,
Height = 768,
HorizontalAlignment = HorizontalAlignment.Left,
VerticalAlignment = VerticalAlignment.Top,
Background = new SolidColorBrush(Colors.Blue)
};
Content = root;
Loaded += MainPage_Loaded;
}
void MainPage_Loaded(object sender, RoutedEventArgs e)
{
var parent1 = new Grid {
Background = new SolidColorBrush(Colors.Red),
HorizontalAlignment = HorizontalAlignment.Left,
VerticalAlignment = VerticalAlignment.Top,
Width = 480,
Height = 768
};
root.Children.Add(parent1);
var child1 = new Grid {
Background = new SolidColorBrush(Colors.Yellow),
Width = 768,
HorizontalAlignment = HorizontalAlignment.Left,
VerticalAlignment = VerticalAlignment.Top,
Height = 1228,
RenderTransform = new ScaleTransform() {
CenterX = 0,
CenterY = 0,
ScaleX = 0.625,
ScaleY = 0.625
}
};
parent1.Children.Add(child1);
}
}
Well, looks like it's all due to the fact that the Grid component clip its child elements, as mentioned at http://wpf.2000things.com/tag/clipping. After I changed the code to have a Canvas aground my Grid, the app worked as expected.
I still find this "solution" strange, though.
Related
I've got a custom class for a button with a circular image as I'll be using it multiple times through my program. I thought it'd be pretty simple of creating class, inheriting from Button and slapping my setup into a constructor, but when I'm running the program the buttons are massive and plain (no image or text). Here's my class:
public class ImageButton : Button
{
public Button Button;
public ImageButton(string filename) : this(HorizontalAlignment.Center, VerticalAlignment.Center, filename)
{ }
public ImageButton(HorizontalAlignment hAlignment, VerticalAlignment vAlignment, string filename)
{
Button = new Button
{
Width = 35,
Height = 35,
Background = Brushes.Transparent,
HorizontalAlignment = hAlignment,
BorderBrush = Brushes.Transparent,
VerticalAlignment = vAlignment,
Content = new Image
{
Source = new BitmapImage(new Uri("pack://application:,,,/Resources/" + filename))
}
};
}
}
And here's my implementation of one of the instances
private void SetupHeaders(Grid resultGrid)
{
RowDefinition backbtn = new RowDefinition();
backbtn.Height = new GridLength(0.2, GridUnitType.Star);
resultGrid.RowDefinitions.Add(backbtn);
btn_Return = new ImageButton(HorizontalAlignment.Left, VerticalAlignment.Top, "returnicon.png");
Grid.SetRow(btn_Return, 0);
Grid.SetColumn(btn_Return, 0);
resultGrid.Children.Add(btn_Return);
}
with btn_Return being defined at the top of the class as simply
ImageButton btn_Return;
Here's an image of one of the buttons.
In you constructor you inititalize a Button with you properties and then assign it to a property. You never actually use the initialized button. You are always using ImageButton which is nothing more than an inherited button therefore you get the default behavior.
You have to change your constructor.
public ImageButton(HorizontalAlignment hAlignment, VerticalAlignment vAlignment, string filename)
{
Width = 35;
Height = 35;
Background = Brushes.Transparent;
HorizontalAlignment = hAlignment;
BorderBrush = Brushes.Transparent;
VerticalAlignment = vAlignment;
Content = new Image
{
Source = new BitmapImage(new Uri("pack://application:,,,/Resources/" + filename))
};
}
I have the following simple example in WPF to play a video file using a VideoDrawing object - here is the code behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
MediaTimeline timeline = new MediaTimeline(new Uri(#"c:\test\RedRock-uhd-h264.mp4", UriKind.Absolute));
timeline.RepeatBehavior = RepeatBehavior.Forever;
MediaClock clock = timeline.CreateClock();
MediaPlayer player = new MediaPlayer();
player.Clock = clock;
VideoDrawing drawing = new VideoDrawing();
drawing.Rect = new Rect(0, 0, 820, 600); //<--video size is 620 x 400 same as XAML MainWindow size
drawing.Rect = new Rect(0, 0, 420, 280); //<--video size is 620 x 400 same as XAML MainWindow size
drawing.Rect = new Rect(0, 0, 220, 80); //<--video size is 620 x 400 same as XAML MainWindow size
drawing.Rect = new Rect(0, 0, 1, 1); //<--video size is 620 x 400 same as XAML MainWindow size
drawing.Rect = new Rect(0, 0, 0, 0); //<--video does not show
//drawing.Rect = new Rect(0, 0, 0, 0); //<--video does not show
drawing.Player = player;
DrawingBrush brush = new DrawingBrush(drawing);
this.Background = brush;
}
}
and here is the XAML:
<Window x:Class="MyMediaPlayer.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MediaPlayer in WPF" Width="620" Height="400"
WindowStyle="None"
ShowInTaskbar="True"
AllowsTransparency="True"
Background="Transparent"
WindowStartupLocation="Manual"
Left="0"
Top="0">
</Window>
look at the lines "drawing.Rect = new Rect(…) above and note the comments - no matter what size I set the Rect to - the video always plays at the size of the XAML MainWindow size (620, 400), however I have to set at least some Rect size I can't set it to 0 or comment it out. It seems like the video ought to play at the Rect size set, unless it is larger than the XAML MainWindow? What is it I don't understand about what I am doing and why doesn't the video play to the size of the Rect?
Set the stretch mode to None:
brush.Stretch = Stretch.None;
The problem with this of course is that you now don't have a way to set the color of the area around the player. If you want control of that then you'll have to switch to a VisualBrush and use a MediaElement instead:
// create a grid and bind it to the parent window's size
var grid = new Grid { Background = Brushes.CornflowerBlue }; // <- sets background color
grid.SetBinding(WidthProperty, new Binding
{
RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(Window), 1),
Path = new PropertyPath("ActualWidth"),
Mode = BindingMode.OneWay,
UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
});
grid.SetBinding(HeightProperty, new Binding
{
RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(Window), 1),
Path = new PropertyPath("ActualHeight"),
Mode = BindingMode.OneWay,
UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
});
// add the media player
grid.Children.Add(new MediaElement
{
Source = new Uri("yourvideo.mp4", UriKind.RelativeOrAbsolute),
LoadedBehavior = MediaState.Play,
Stretch = Stretch.Fill,
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
Width = 640, // <-- video size
Height = 480
});
// wrap it all up in a visual brush
this.Background = new VisualBrush { Visual = grid };
I would like to add an Ellipse to some MenuItems of my ContextMenu.
Sadly I could not get this to work [Nothing is displayed].
Canvas canvas = new Canvas() { Height = 16, Width = 16 };
canvas.Children.Add(new System.Windows.Shapes.Ellipse()
{
Height = 16,
Width = 16,
Fill = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Colors.Red)
});
System.Windows.Media.Imaging.RenderTargetBitmap bmp = new System.Windows.Media.Imaging.RenderTargetBitmap((int)canvas.Width, (int)canvas.Height, 96, 96, System.Windows.Media.PixelFormats.Default);
bmp.Render(canvas);
MenuItem tmp = new MenuItem();
tmp.Header = "Away";
tmp.Icon = new System.Windows.Controls.Image()
{
Source = bmp
};
AddContextMenuEntry(tmp);
What am I missing or what is wrong here ?
Expected result would be sth. like this:
No image required: Icon is object. It can be any content: Any visual element, any value, any instance of any class. If it's a viewmodel it'll need an implicit DataTemplate. But a red circle is a snap.
MenuItem tmp = new MenuItem();
tmp.Header = "Away";
tmp.Icon = new System.Windows.Shapes.Ellipse()
{
Height = 16,
Width = 16,
Fill = System.Windows.Media.Brushes.Red
};
If you want something more complicated, you could have given it the Canvas instead, with the Ellipse and other child elements.
I have to draw a circle in a grid. That grid has to adapt proportionally to height and width defined by the Column/Row definition of its parent grid.
Now if I put stretch it will fill all the space and become an ellipsis while I want it to be a circle.
Ok in short the parent grid adapts proportionally like that
then in a routine I add the following code:
public void RadialPercentage(Grid grd )
{
Ellipse elpExt = new Ellipse();
elpExt.Stroke = Brushes.Green;
elpExt.StrokeThickness = 4;
//elpExt.Margin = new Thickness(0);
//elpExt.HorizontalAlignment = HorizontalAlignment.Center;
elpExt.VerticalAlignment = VerticalAlignment.Stretch;
grd.Children.Add(elpExt);
Ellipse elpInt = new Ellipse();
elpInt.Stroke = Brushes.Blue;
elpInt.StrokeThickness = 4;
elpInt.Margin = new Thickness(20);
//elpInt.Width = elpInt.Height = dim-20;
//elpInt.HorizontalAlignment = HorizontalAlignment.Center;
elpInt.VerticalAlignment = VerticalAlignment.Stretch;
grd.Children.Add(elpInt);
return;
}
but the effect is the following:
so it stretches both vertically and horizontally even if I only put the vertical and not the horizontal constraint. If I set it to center the ellipse collapses.
To solve the problem even I am not sure that this is the right thing to do I tried to take a look of the weight/heigth of the parent grid but obviously both those values and the actual values are set to zero.
thanks for helping
Patrick
What about setting Width's binding to ActualHeight of the ellipse and set HorizontalAlignment to Center? Something like this:
var ellipse = new Ellipse();
var binding = new Binding(Ellipse.ActualHeightProperty.Name)
{
RelativeSource = new RelativeSource(RelativeSourceMode.Self),
Mode = BindingMode.OneWay
};
ellipse.VerticalAlignment = VerticalAlignment.Stretch;
ellipse.HorizontalAlignment = HorizontalAlignment.Center;
BindingOperations.SetBinding(ellipse, Ellipse.WidthProperty, binding);
You can update the size of your Ellipse each time the parent Grid is resized.
You should add to your Grid the SizeChanged Event. XAML example:
<Grid Name = "MyGrid"
SizeChanged = "MyGridSizeChanged">
<!-- rows and columns definitions -->
<Ellipse Name = "MyEllipse"
Grid.Row = "i"
Grid.Column = "j" />
</Grid>
Now, each time the Grid is resized the function MyGridSizeChanged will executed. You should add into it code, which set sizes of your Ellipse equal to smallest side of contained cell. C# example:
void MyGridSizeChanged(object sender, SizeChangedEventArgs e) {
if (sender is Grid myGrid) {
var cellHeight = myGrid.RowDefinitions[Grid.GetRow(MyEllipse)].ActualHeight;
var cellWidth = myGrid.ColumnDefinitions[Grid.GetColumn(MyEllipse)].ActualWidth;
var newSize = Math.Min(cellHeight, cellWidth);
MyEllipse.Height = newSize;
MyEllipse.Width = newSize;
}
}
I have something like this so far for my view:
public StackLayout OffersSlideViewCarouselChild(Offer offer)
{
Image productImage = new Image
{
Source = ImageSource.FromUri(new Uri(offer.Image.Replace("https://", "http://"))),
HeightRequest = 270,
WidthRequest = 270,
Aspect = Aspect.AspectFit
};
var topStackLayout = new StackLayout
{
Spacing = 0
};
topStackLayout.Children.Add(productImage);
StackLayout contentStackLayout = new StackLayout
{
Spacing = 0,
Padding = new Thickness(5, 10, 5, 10),
Orientation = StackOrientation.Vertical
};
var savedBtn = SavedButtonLayout(offer.IsSelected, offer.Id);
var redeemBtn = RedeemBtnLayout(offer.Id);
var timeRemainingLabel = TimeRemainingLayout(offer, offer.Id);
contentStackLayout.Children.Add(new UILabel(16) {
Text = offer.ProductName,
TextColor = ColorHelper.FromHex(CoreTheme.COLOR_OFFERCELL_PRODUCT_TEXT),
FontFamily = CoreTheme.FONT_FAMILY_DEFAULT_BOLD,
WidthRequest = DeviceDisplaySettings.defaultwidth,
VerticalTextAlignment = TextAlignment.Center
});
contentStackLayout.Children.Add(new UILabel(14)
{
Text = offer.Headline,
TextColor = ColorHelper.FromHex(CoreTheme.COLOR_OFFERCELL_PRODUCT_TEXT),
FontFamily = CoreTheme.FONT_FAMILY_DEFAULT_BOLD,
WidthRequest = DeviceDisplaySettings.defaultwidth,
VerticalTextAlignment = TextAlignment.Center
});
contentStackLayout.Children.Add(new UILabel(14) {
Text = offer.LongRewardsMessage,
TextColor = ColorHelper.FromHex(CoreTheme.COLOR_DEAL_PAGE_LONG_REWARD_MESSAGE_RED),
FontFamily = CoreTheme.FONT_FAMILY_DEFAULT_BOLD,
WidthRequest = DeviceDisplaySettings.defaultwidth,
VerticalTextAlignment = TextAlignment.Center
});
if (!string.IsNullOrEmpty(offer.PowerMessage)) {
var htmlText = string.Format("<html><body style='color:#9b9b9b'>{0}</body></html>", offer.PowerMessage.Replace(#"\", string.Empty));
var browser = new WebView() {
//HeightRequest = (DeviceDisplaySettings.defaultheight > 600) ? 500 : 400,
HeightRequest = 800,
Source = new HtmlWebViewSource() { Html = htmlText },
};
browser.Navigating += OnNavigating;
contentStackLayout.Children.Add(browser);
}
var nestedStackLayout = new StackLayout()
{
VerticalOptions = LayoutOptions.FillAndExpand
};
nestedStackLayout.Children.Add(topStackLayout);
nestedStackLayout.Children.Add(timeRemainingLabel);
nestedStackLayout.Children.Add(contentStackLayout);
var mainScrollView = new ScrollView()
{
Padding = new Thickness(0, 0, 0, 10),
VerticalOptions = LayoutOptions.FillAndExpand,
Orientation = ScrollOrientation.Vertical,
Content = nestedStackLayout
};
var mainStackLayout = new StackLayout()
{
Spacing = 5,
Padding = new Thickness(0, 0, 0, 0),
VerticalOptions = LayoutOptions.Fill,
HorizontalOptions = LayoutOptions.Fill,
Orientation = StackOrientation.Vertical,
Children = { savedBtn, mainScrollView, redeemBtn }
};
return mainStackLayout;
}
private StackLayout SavedButtonLayout(bool isSelected, int offerid)
{
int buttonsToShow = 2;
bool displaySaveButton = true;
if (IsPremisesOffer (offerid)) {
buttonsToShow = 3;
displaySaveButton = false;
}
btnShare = new UIFieldDefinition(_pageFieldDefinition.ShareButtonDefinition);
btnShare.Text = "SHARE";
btnShare.ClassId = offerid.ToString();
btnShare.WidthRequest = (DeviceDisplaySettings.defaultwidth / buttonsToShow) - 40;
btnShare.BackgroundColor = Color.FromRgb(167, 188, 33);
btnShare.VerticalContentAlignment = TextAlignment.Center;
btnShare.HandleClick(btnShare_Clicked);
btnSave = new UIFieldDefinition(_pageFieldDefinition.SaveButtonDefinition);
btnSave.Text = isSelected ? "UNSAVE" : "SAVE";
btnSave.ClassId = offerid.ToString();
btnSave.WidthRequest = (DeviceDisplaySettings.defaultwidth / buttonsToShow) - 40;
btnSave.BackgroundColor = Color.FromRgb(167, 188, 33);
btnSave.VerticalContentAlignment = TextAlignment.Center;
btnSave.HandleClick(btnSave_Clicked);
rl = new StackLayout {
Spacing = 10,
Orientation = StackOrientation.Horizontal,
BackgroundColor = Color.FromRgb(196, 221, 57),
Padding = new Thickness(40, 5, 5, 5),
WidthRequest = DeviceDisplaySettings.defaultwidth
};
rl.Children.Add(btnShare);
if (displaySaveButton) rl.Children.Add(btnSave);
return rl;
}
public UIFieldDefinition RedeemBtnLayout(int offerid)
{
int buttonsToShow = 1;
btnRedeem = new UIFieldDefinition(_pageFieldDefinition.RedeemButtonDefinition);
btnRedeem.Text = "REDEEM NOW";
btnRedeem.ClassId = offerid.ToString();
btnRedeem.WidthRequest = (DeviceDisplaySettings.defaultwidth / buttonsToShow) - 10;
// btnRedeem.HorizontalOptions = LayoutOptions.FillAndExpand;
// btnRedeem.VerticalOptions = LayoutOptions.EndAndExpand;
btnRedeem.HandleClick(btnRedeem_Clicked);
return btnRedeem;
}
However, I am noticing that the Redeem button does not even display on the view (It's supposed to be fixed on the bottom).
The scrollview works but the buttom is missing. Why?
Please let me know if you need further code details.
Moving here from comments above. There are two separate issues from what I can tell, and as far as I can tell, are unrelated:
The WebView, nested inside the ScrollView, is not big enough to fully display the content.
The button that is supposed to be at the bottom of the screen is not displaying.
For both of them, the answer is probably in how you are setting HeightRequest. There have been a lot of suggestions by myself and other commenters to change or get rid of some of the HeightRequest settings, and I'm not sure of the current state of your source code. So assuming those are still there:
For solving the WebView issue, read How can I add HTML to a Stacklayout inside a Scrollview in Xamarin forms?. This will let you figure out the right HeightRequest to use. The short answer is that depending on exactly what you want to happen, you may need a custom renderer. Note that the HeightRequest for the WebView will not affect any layout outside of the ScrollView.
For solving the issue of the button not appearing, get rid of the HeightRequest setting on the ScrollView, and the VerticalOptions on the StackLayout created in SavedButtonLayout.
I am assuming you did the experiment suggested above to make sure that the redeemBtn will render if placed before the ScrollView, and it does show up then. If not, you first need to fix that.
If you have "fixed" this by changing the HeightRequest then your real problem is the fixed pixel size of all your views and layouts, I recommend you DON'T use fixed pixel sizes for different screen resolution this will be a bigger problem later, What you can do is get the Screen size and do the math to fit all your elements of the view, one way to get the width and height of the screen is on the OnSizeChanged event of Pages (Like ContentPage), something like this:
SizeChanged += SizeChanged;
void SizeChanged (object sender, EventArgs e)
{
Layout.WidthRequest = Width * 0.3;
Layout.HeightRequest = Height * 0.35;
}
Your layout is pretty busy. A few things:
Set VerticalOptions to EndAndExpand for redeemBtn.
Set VerticalOptions to StartAndExpand for savedBtn.
Set VerticalOptions to Fill for mainScrollView.
Set VerticalOptions to FillAndExpand for mainRelLayout.
Set VerticalOptions and HorizontalOptions to Fill for
mainStackLayout.
I think that will get you to where you want to be.
The options that include "Expand" will grow the element to accommodate the desired height of its contents.