Is there any way to force execute the OnSizeAllocated method? - xaml

I have created a view using Xaml code behind. I did it using the code behind because I wanted to change the layout of the view based on the device orientation. So, the problem which I am facing is that the OnSizeAllocated method is being called after the view is loaded. So, it is unable to change the layout as per the device orientation. I just want to know if there is any way to invoke the OnSizeAllocated method before the view is loaded. Please click on the below link to view the code:
Please click Here to view the Code

1.Rearrange the Page
you could check if width is greater than height to determine if the device is now in landscape or portrait:
public partial class Page13 : ContentPage
{
private double _width ;
private double _height ;
private Grid grid;
private Label label;
private Entry entry;
private Button button;
public Page13 ()
{
_width = this.Width;
_height = this.Height;
label = new Label(){Text = "i am a laber"};
entry = new Entry(){WidthRequest = 200};
button = new Button(){Text = "Submit"};
grid = new Grid();
UpdateLayout();
StackLayout stackLayout = new StackLayout();
stackLayout.Children.Add(grid);
Content = stackLayout;
}
protected override void OnSizeAllocated(double width, double height)
{
base.OnSizeAllocated(width, height);
if (_width != width || _height != height)
{
_width = width;
_height = height;
UpdateLayout();
}
}
void UpdateLayout()
{
grid.RowDefinitions.Clear();
grid.ColumnDefinitions.Clear();
grid.Children.Clear();
if (_width > _height)
{
ScreenRotatedToLandscape();
}
else
{
ScreenRotatedToPortrait();
}
}
private void ScreenRotatedToLandscape()
{
grid.RowDefinitions.Add(new RowDefinition(){Height = new GridLength(1,GridUnitType.Auto)});
grid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Auto) });
grid.ColumnDefinitions.Add(new ColumnDefinition(){Width = new GridLength(1,GridUnitType.Auto)});
grid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(1, GridUnitType.Auto) });
grid.Children.Add(label,0,0);
grid.Children.Add(entry, 1, 0);
grid.Children.Add(button, 0, 1);
Grid.SetColumnSpan(button,2);
}
private void ScreenRotatedToPortrait()
{
grid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Auto) });
grid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Auto) });
grid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Auto) });
grid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(1, GridUnitType.Auto) });
grid.Children.Add(label, 0, 0);
grid.Children.Add(entry, 0, 1);
grid.Children.Add(button, 0, 2);
}
}
This is the recommended implementation pulled right from the Xamarin.Forms documentation.
2.Using Xamarin.Essentials
It adds additional functionality to cross-platform applications built in Xamarin. One of these new features is the ability to ping the device for the current orientation by accessing the DeviceDisplay.ScreenMetrics.Orientation property. This returns the current device orientation, which can be used to determine which layout to render.
it's similar to the one above
private bool IsPortrait;
public Page13 ()
{
...
IsPortrait = DeviceDisplay.ScreenMetrics.Orientation == ScreenOrientation.Portrait;
UpdateLayout();
...
}
void UpdateLayout()
{
grid.RowDefinitions.Clear();
grid.ColumnDefinitions.Clear();
grid.Children.Clear();
if (IsPortrait)
{
ScreenRotatedToPortrait();
}
else
{
ScreenRotatedToLandscape();
}
}

You can't force run that since the SizeAllocation hasn't changed, but you could do this to get orientation on initial load:
If you add the Xamarin.Essentials nuget package, as you can see here, you can get the orientation using this line of code DeviceDisplay.MainDisplayInfo.Orientation and you will get Landscape, Portrait, Square, or Unknown.
If you don't want to add the package, you can just use Application.Current.MainPage.Width and Application.Current.MainPage.Height to figure out orientation.

Related

Sliding in and out using xamarin

I am trying to implement this kind of design as shown in fig on my page, how can I achieve this in Xamarin.forms mainly in shared project. As I am trying to reduce the complexity by doing in platform specific.
I tried using this even https://github.com/XAM-Consulting/SlideOverKit .
But the issue is I can't slide in the menu when its open (i.e after touching on page I want the menu to hide) but it doesn't happen. We need to manually drag that out to close it.
So please let me know how to achieve this.
Thanks
Based on your description, I modifed my code,there is a GIF
When you click the ImageButton, you should call this method this.HideMenu();
code of QuickInnerMenuPage
public QuickInnerMenuPage()
{
Content = new StackLayout
{
VerticalOptions = LayoutOptions.Center,
HorizontalOptions = LayoutOptions.Center,
Children = {
new Label(){Text="1222"}
}
};
this.SlideMenu = new QuickInnerMenuView(MenuOrientation.RightToLeft);
QuickInnerMenuView.ib.Clicked += (o, e) =>
{
this.HideMenu();
};
}
}
There is code of QuickInnerMenuView
public class QuickInnerMenuView : SlideMenuView
{
public static ImageButton ib;
public QuickInnerMenuView (MenuOrientation orientation)
{
ib = new ImageButton
{
Source = "Happy.png",
WidthRequest = 25,
HeightRequest = 25,
};
var mainLayout = new StackLayout {
Spacing = 15,
Children = {
ib,
new ImageButton {
Source = "Home.png",
WidthRequest = 25,
HeightRequest = 25,
},
new ImageButton {
Source = "MessageFilled.png",
WidthRequest = 25,
HeightRequest = 25,
},
new ImageButton {
Source = "Settings.png",
WidthRequest = 25,
HeightRequest = 25,
},
}
};
// In this case the IsFullScreen must set false
this.IsFullScreen = false;
this.BackgroundViewColor = Color.Transparent;
// You must set BackgroundColor,
// and you cannot put another layout with background color cover the whole View
// otherwise, it cannot be dragged on Android
this.BackgroundColor = Color.FromHex ("#C82630");
this.MenuOrientations = orientation;
if (orientation == MenuOrientation.BottomToTop) {
mainLayout.Orientation = StackOrientation.Vertical;
mainLayout.Children.Insert (0, new Image {
Source = "DoubleUp.png",
WidthRequest = 25,
HeightRequest = 25,
});
mainLayout.Padding = new Thickness (0, 5);
// In this case, you must set both WidthRequest and HeightRequest.
this.WidthRequest = 50;
this.HeightRequest = 200;
// A little bigger then DoubleUp.png image size, used for user drag it.
this.DraggerButtonHeight = 30;
// In this menu direction you must set LeftMargin.
this.LeftMargin = 100;
}
Do you add want to achieve it like following GIF.
If so,when you used SlideMenu, you should Create a new view that inherit's from MenuContainerPage like following code.Note:MenuContainerPage comes from SlideOverKit
public class QuickInnerMenuPage: MenuContainerPage
{
public QuickInnerMenuPage()
{
Content = new StackLayout
{
VerticalOptions = LayoutOptions.Center,
HorizontalOptions = LayoutOptions.Center,
Children = {
new Label(){Text="1222"}
}
};
this.SlideMenu = new QuickInnerMenuView(MenuOrientation.RightToLeft);
}
}
Here is my demo.you could refer to it.
https://github.com/851265601/SlideOverKitDemo

Cannot show user input with a textfield

I'm trying to use a TextField to get some user input:
public void render() {
Gdx.gl.glClear(GL11.GL_COLOR_BUFFER_BIT);
Gdx.gl.glClearColor(0, 0, 0, 0);
batch.begin();
batch.end();
stage = new Stage();
Gdx.input.setInputProcessor(stage);
Skin skin = new Skin(Gdx.files.internal("assets/uiskin.json"));
TextButton btnLogin = new TextButton("Click", skin);
btnLogin.setPosition(300, 300);
btnLogin.setSize(300, 60);
btnLogin.addListener(new ClickListener() {
public boolean touchDown(InputEvent e, float x, float y, int point, int button) {
System.out.println(txfUsername.getText());
return false;
}
});
txfUsername = new TextField("", skin);
txfUsername.setPosition(300, 250);
txfUsername.setSize(300, 40);
stage.addActor(txfUsername);
stage.addActor(btnLogin);
stage.act();
stage.draw();
}
All I get is a blank field. The user can't interact with it in any way.
I followed the instructions in this video
How do I make the textfield editable?
You use txfUsername = new TextField ("", skin); in your render method. That creates a new TextField from scratch on each render.
public void render() {
Gdx.gl.glClearColor(0, 0, 0, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
batch.begin();
// do other rendering ...
batch.end();
Gdx.app.log("MyTextField", txfUsername.getText());
stage.act(Gdx.graphics.getDeltaTime());
stage.draw();
}
In your class variables:
private TextButton btnLogin;
TextField txfUsername;
In your method show or create (not render):
#Override
public void show() {
btnLogin = new TextButton("Click", skin);
btnLogin.setPosition(300, 300);
btnLogin.setSize(300, 60);
btnLogin.addListener(new ClickListener() {
public boolean touchDown(InputEvent e, float x, float y, int point, int button) {
System.out.println(txfUsername.getText());
return false;
}
});
txfUsername = new TextField("", skin);
txfUsername.setPosition(300, 250);
txfUsername.setSize(300, 40);
stage.addActor(txfUsername);
stage.addActor(btnLogin);
}
Use txfUsername.getText(); written for the field
Edit: I do not know how you worked with GL11, if GL10, I could understand a you malfunction, if you get error in GL10, you should update libgdx, GL10 is an interface and I think last versions for in libgdx not bring already

Windows Store Apps: animate control when visibility changes?

In my app I have A grid with visibility bound to a property in the view model.
What I want to do is when the visibility property changes at the view model, the grid fades in or out according to the visibility value: Visible/Collapsed.
how can I achieve this ?
Inspired by the answer of "HDW Production", here's the code for Windows Store and Windows Phone Store apps:
public class FadingVisibilityGrid : Grid
{
public static readonly DependencyProperty DeferredVisibilityProperty = DependencyProperty.Register(
"DeferredVisibility", typeof (Visibility), typeof (FadingVisibilityGrid), new PropertyMetadata(default(Visibility), DeferredVisibilityChanged));
private static void DeferredVisibilityChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var newVisibility = (Visibility)e.NewValue;
var grid = (FadingVisibilityGrid)sender;
var animation = new DoubleAnimation
{
Duration = new Duration(TimeSpan.FromMilliseconds(200))
};
Storyboard.SetTarget(animation, grid);
Storyboard.SetTargetProperty(animation, "Grid.Opacity");
grid.FadeStoryBoard.Stop();
grid.FadeStoryBoard = new Storyboard();
grid.FadeStoryBoard.Children.Add(animation);
if (newVisibility == Visibility.Visible)
{
animation.From = 0;
animation.To = 1;
grid.Visibility = Visibility.Visible;
grid.FadeStoryBoard.Begin();
}
else
{
animation.From = 1;
animation.To = 0;
grid.FadeStoryBoard.Completed += (o, o1) =>
{
grid.Visibility = newVisibility;
};
grid.FadeStoryBoard.Begin();
}
}
public Visibility DeferredVisibility
{
get { return (Visibility) GetValue(DeferredVisibilityProperty); }
set { SetValue(DeferredVisibilityProperty, value); }
}
private Storyboard _fadeStoryBoard = new Storyboard();
public Storyboard FadeStoryBoard
{
get { return _fadeStoryBoard; }
set { _fadeStoryBoard = value; }
}
}
You need a new DependencyProperty, either by inheriting from Grid and adding one or by creating an attached property. Let's call it DeferredVisibility and let it be of type Visibility.
When DeferredVisibility is changed to Visible, set the Visibility to Visible and animate the opacity from 0 to 1.
When DeferredVisibility is changed to Collapsed, animate the opacity from 1 to 0 and THEN set the Visibility to Collapsed.

Why are my screenshots only black?

could someone tell me why my screenshots are only black? I am still learning and couldnt find a clue why they are only black.
This is my Utility class
static class XNAUtilities
{
private static RenderTarget2D ssTexture;
private static KeyboardState currentState, previousState;
private static int counter;
public static void TakeScreenShot(GraphicsDevice device, Keys theKey)
{
// Take Screenshot
currentState = Keyboard.GetState();
if (currentState.IsKeyDown(theKey) && previousState.IsKeyUp(theKey))
{
//device.SetRenderTarget(null);
PresentationParameters pparameters = device.PresentationParameters;
ssTexture = new RenderTarget2D(device, pparameters.BackBufferWidth, pparameters.BackBufferHeight, false, SurfaceFormat.Color, DepthFormat.None); //??
FileStream fileStream = new System.IO.FileStream(#"Screenshot" + "_" + counter + ".png", System.IO.FileMode.CreateNew);
ssTexture.SaveAsPng(fileStream, pparameters.BackBufferWidth, pparameters.BackBufferHeight);
counter++;
}
previousState = currentState;
}
}
}
This is my Update and Draw from Game1.cs
protected override void Update(GameTime gameTime)
{
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
this.Exit();
myModelRotation += MathHelper.ToRadians(1f);
// Take a Screenshot
XNAUtilities.TakeScreenShot(GraphicsDevice, Keys.F8);
base.Update(gameTime);
}
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
Matrix[] transforms = new Matrix[myModel.Bones.Count];
myModel.CopyAbsoluteBoneTransformsTo(transforms);
foreach (ModelMesh mesh in myModel.Meshes)
{
foreach (BasicEffect effects in mesh.Effects)
{
effects.EnableDefaultLighting();
effects.World = transforms[mesh.ParentBone.Index]
* Matrix.CreateRotationY(myModelRotation)
* Matrix.CreateTranslation(myModelPosition);
effects.View = Matrix.CreateLookAt(new Vector3(200, 100, 400), Vector3.Zero, Vector3.Up);
effects.Projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(45f),
GraphicsDevice.Viewport.AspectRatio, 1, 5000);
}
mesh.Draw();
}
smileySprite.DrawSprites(GraphicsDevice, spriteBatch, new Vector2(10,10), Color.White);
base.Draw(gameTime);
}
You're not actually rendering to your render target. So you're saving the blank target.
You need to wrap your scene drawing like so:
GraphicsDevice.SetRenderTarget(ssTexture);
// Render your scene here
GraphicsDevice.SetRenderTarget(null);
// Now you can save your render target as a texture

How to make a custom ComboBox (OwnerDrawFixed) looks 3D like the standard ComboBox?

I am making a custom ComboBox, inherited from Winforms' standard ComboBox. For my custom ComboBox, I set DrawMode to OwnerDrawFixed and DropDownStyle to DropDownList. Then I write my own OnDrawItem method. But I ended up like this:
How do I make my Custom ComboBox to look like the Standard one?
Update 1: ButtonRenderer
After searching all around, I found the ButtonRenderer class. It provides a DrawButton static/shared method which -- as the name implies -- draws the proper 3D button. I'm experimenting with it now.
Update 2: What overwrites my control?
I tried using the Graphics properties of various objects I can think of, but I always fail. Finally, I tried the Graphics of the form, and apparently something is overwriting my button.
Here's the code:
Protected Overrides Sub OnDrawItem(ByVal e As System.Windows.Forms.DrawItemEventArgs)
Dim TextToDraw As String = _DefaultText
__Brush_Window.Color = Color.FromKnownColor(KnownColor.Window)
__Brush_Disabled.Color = Color.FromKnownColor(KnownColor.GrayText)
__Brush_Enabled.Color = Color.FromKnownColor(KnownColor.WindowText)
If e.Index >= 0 Then
TextToDraw = _DataSource.ItemText(e.Index)
End If
If TextToDraw.StartsWith("---") Then TextToDraw = StrDup(3, ChrW(&H2500)) ' U+2500 is "Box Drawing Light Horizontal"
If (e.State And DrawItemState.ComboBoxEdit) > 0 Then
'ButtonRenderer.DrawButton(e.Graphics, e.Bounds, VisualStyles.PushButtonState.Default)
Else
e.DrawBackground()
End If
With e
If _IsEnabled(.Index) Then
.Graphics.DrawString(TextToDraw, Me.Font, __Brush_Enabled, .Bounds.X, .Bounds.Y)
Else
'.Graphics.FillRectangle(__Brush_Window, .Bounds)
.Graphics.DrawString(TextToDraw, Me.Font, __Brush_Disabled, .Bounds.X, .Bounds.Y)
End If
End With
TextToDraw = Nothing
ButtonRenderer.DrawButton(Me.Parent.CreateGraphics, Me.ClientRectangle, VisualStyles.PushButtonState.Default)
'MyBase.OnDrawItem(e)
End Sub
And here's the result:
Replacing Me.Parent.CreateGraphics with e.Graphics got me this:
And doing the above + replacing Me.ClientRectangle with e.Bounds got me this:
Can anyone point me whose Graphics I must use for the ButtonRenderer.DrawButton method?
PS: The bluish border is due to my using PushButtonState.Default instead of PushButtonState.Normal
I Found An Answer! (see below)
I forgot where I found the answer... I'll edit this answer when I remember.
But apparently, I need to set the Systems.Windows.Forms.ControlStyles flags. Especially the ControlStyles.UserPaint flag.
So, my New() now looks like this:
Private _ButtonArea as New Rectangle
Public Sub New()
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
MyBase.SetStyle(ControlStyles.Opaque Or ControlStyles.UserPaint, True)
MyBase.DrawMode = Windows.Forms.DrawMode.OwnerDrawFixed
MyBase.DropDownStyle = ComboBoxStyle.DropDownList
' Cache the button's modified ClientRectangle (see Note)
With _ButtonArea
.X = Me.ClientRectangle.X - 1
.Y = Me.ClientRectangle.Y - 1
.Width = Me.ClientRectangle.Width + 2
.Height = Me.ClientRectangle.Height + 2
End With
End Sub
And now I can hook into the OnPaint event:
Protected Overrides Sub OnPaint(ByVal e As System.Windows.Forms.PaintEventArgs)
If Me.DroppedDown Then
ButtonRenderer.DrawButton(Me.CreateGraphics, _ButtonArea, VisualStyles.PushButtonState.Pressed)
Else
ButtonRenderer.DrawButton(Me.CreateGraphics, _ButtonArea, VisualStyles.PushButtonState.Normal)
End If
MyBase.OnPaint(e)
End Sub
Note: Yes, the _ButtonArea rectangle must be enlarged by 1 pixel to all directions (up, down, left, right), or else there will be a 1-pixel 'perimeter' around the ButtonRenderer that shows garbage. Made me crazy for awhile until I read that I must enlarge the Control's rect for ButtonRenderer.
I had this problem myself and the reply by pepoluan got me started. I still think a few things are missing in order to get a ComboBox with looks and behavior similar to the standard ComboBox with DropDownStyle=DropDownList though.
DropDownArrow
We also need to draw the DropDownArrow. I played around with the ComboBoxRenderer, but it draws a dark border around the area of the drop down arrow so that didn't work.
My final solution was to simply draw a similar arrow and render it onto the button in the OnPaint method.
Hot Item Behavior
We also need to ensure our ComboBox has a hot item behavior similar to the standard ComboBox. I don't know of any simple and reliable method to know when a mouse is no longer above the control. Therefore I suggest using a Timer that checks at each tick whether the mouse is still over the control.
Edit
Just added a KeyUp event handler to make sure the control would update correctly when a selection was made using the keyboard. Also made a minor correction of where the text was rendered, to ensure it is more similar to the vanilla combobox' text positioning.
Below is the full code of my customized ComboBox. It allows you to display images on each item and is always rendered as in the DropDownList style, but hopefully it should be easy to accommodate the code to your own solution.
using System;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Design;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Windows.Forms;
using System.Windows.Forms.VisualStyles;
namespace CustomControls
{
/// <summary>
/// This is a special ComboBox that each item may conatins an image.
/// </summary>
public class ImageComboBox : ComboBox
{
private static readonly Size arrowSize = new Size(18, 20);
private bool itemIsHot;
/* Since properties such as SelectedIndex and SelectedItems may change when the mouser is hovering over items in the drop down list
* we need a property that will store the item that has been selected by comitted selection so we know what to draw as the selected item.*/
private object comittedSelection;
private readonly ImgHolder dropDownArrow = ImgHolder.Create(ImageComboBox.DropDownArrow());
private Timer hotItemTimer;
public Font SelectedItemFont { get; set; }
public Padding ImageMargin { get; set; }
//
// Summary:
// Gets or sets the path of the property to use as the image for the items
// in the System.Windows.Forms.ListControl.
//
// Returns:
// A System.String representing a single property name of the System.Windows.Forms.ListControl.DataSource
// property value, or a hierarchy of period-delimited property names that resolves
// to a property name of the final data-bound object. The default is an empty string
// ("").
//
// Exceptions:
// T:System.ArgumentException:
// The specified property path cannot be resolved through the object specified by
// the System.Windows.Forms.ListControl.DataSource property.
[DefaultValue("")]
[Editor("System.Windows.Forms.Design.DataMemberFieldEditor, System.Design, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", typeof(UITypeEditor))]
public string ImageMember { get; set; }
public ImageComboBox()
{
base.SetStyle(ControlStyles.Opaque | ControlStyles.UserPaint, true);
//All the elements in the control are drawn manually.
base.DrawMode = DrawMode.OwnerDrawFixed;
//Specifies that the list is displayed by clicking the down arrow and that the text portion is not editable.
//This means that the user cannot enter a new value.
//Only values already in the list can be selected.
this.DropDownStyle = ComboBoxStyle.DropDownList;
//using DrawItem event we need to draw item
this.DrawItem += this.ComboBoxDrawItemEvent;
this.hotItemTimer = new Timer();
this.hotItemTimer.Interval = 250;
this.hotItemTimer.Tick += this.HotItemTimer_Tick;
this.MouseEnter += this.ImageComboBox_MouseEnter;
this.KeyUp += this.ImageComboBox_KeyUp;
this.SelectedItemFont = this.Font;
this.ImageMargin = new Padding(4, 4, 5, 4);
this.SelectionChangeCommitted += this.ImageComboBox_SelectionChangeCommitted;
this.SelectedIndexChanged += this.ImageComboBox_SelectedIndexChanged;
}
private static Image DropDownArrow()
{
var arrow = new Bitmap(8, 4, PixelFormat.Format32bppArgb);
using (Graphics g = Graphics.FromImage(arrow))
{
g.CompositingQuality = CompositingQuality.HighQuality;
g.FillPolygon(Brushes.Black, ImageComboBox.CreateArrowHeadPoints());
}
return arrow;
}
private static PointF[] CreateArrowHeadPoints()
{
return new PointF[4] { new PointF(0, 0), new PointF(7F, 0), new PointF(3.5F, 3.5F), new PointF(0, 0) };
}
private static void DrawComboBoxItem(Graphics g, string text, Image image, Rectangle itemArea, int itemHeight, int itemWidth, Padding imageMargin
, Brush brush, Font font)
{
if (image != null)
{
// recalculate margins so image is always approximately vertically centered
int extraImageMargin = itemHeight - image.Height;
int imageMarginTop = Math.Max(imageMargin.Top, extraImageMargin / 2);
int imageMarginBotttom = Math.Max(imageMargin.Bottom, extraImageMargin / 2);
g.DrawImage(image, itemArea.X + imageMargin.Left, itemArea.Y + imageMarginTop, itemHeight, itemHeight - (imageMarginBotttom
+ imageMarginTop));
}
const double TEXT_MARGIN_TOP_PROPORTION = 1.1;
const double TEXT_MARGIN_BOTTOM_PROPORTION = 2 - TEXT_MARGIN_TOP_PROPORTION;
int textMarginTop = (int)Math.Round((TEXT_MARGIN_TOP_PROPORTION * itemHeight - g.MeasureString(text, font).Height) / 2.0, 0);
int textMarginBottom = (int)Math.Round((TEXT_MARGIN_BOTTOM_PROPORTION * itemHeight - g.MeasureString(text, font).Height) / 2.0, 0);
//we need to draw the item as string because we made drawmode to ownervariable
g.DrawString(text, font, brush, new RectangleF(itemArea.X + itemHeight + imageMargin.Left + imageMargin.Right, itemArea.Y + textMarginTop
, itemWidth, itemHeight - textMarginBottom));
}
private string GetDistplayText(object item)
{
if (this.DisplayMember == string.Empty) { return item.ToString(); }
else
{
var display = item.GetType().GetProperty(this.DisplayMember).GetValue(item).ToString();
return display ?? item.ToString();
}
}
private Image GetImage(object item)
{
if (this.ImageMember == string.Empty) { return null; }
else { return item.GetType().GetProperty(this.ImageMember).GetValue(item) as Image; }
}
private void ImageComboBox_SelectionChangeCommitted(object sender, EventArgs e)
{
this.comittedSelection = this.Items[this.SelectedIndex];
}
private void HotItemTimer_Tick(object sender, EventArgs e)
{
if (!this.RectangleToScreen(this.ClientRectangle).Contains(Cursor.Position)) { this.TurnOffHotItem(); }
}
private void ImageComboBox_KeyUp(object sender, KeyEventArgs e)
{
this.Invalidate();
}
private void ImageComboBox_MouseEnter(object sender, EventArgs e)
{
this.TurnOnHotItem();
}
private void ImageComboBox_SelectedIndexChanged(object sender, EventArgs e)
{
if (!this.DroppedDown)
{
if (this.SelectedIndex > -1) { this.comittedSelection = this.Items[this.SelectedIndex]; }
else { this.comittedSelection = null; }
}
}
private void TurnOnHotItem()
{
this.itemIsHot = true;
this.hotItemTimer.Enabled = true;
}
private void TurnOffHotItem()
{
this.itemIsHot = false;
this.hotItemTimer.Enabled = false;
this.Invalidate(this.ClientRectangle);
}
/// <summary>
/// Draws overridden items.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void ComboBoxDrawItemEvent(object sender, DrawItemEventArgs e)
{
//Draw backgroud of the item
e.DrawBackground();
if (e.Index != -1)
{
Brush brush;
if (e.State.HasFlag(DrawItemState.Focus) || e.State.HasFlag(DrawItemState.Selected)) { brush = Brushes.White; }
else { brush = Brushes.Black; }
object item = this.Items[e.Index];
ImageComboBox.DrawComboBoxItem(e.Graphics, this.GetDistplayText(item), this.GetImage(item), e.Bounds, this.ItemHeight, this.DropDownWidth
, new Padding(0, 1, 5, 1), brush, this.Font);
}
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
// define the area of the control where we will write the text
var topTextRectangle = new Rectangle(e.ClipRectangle.X - 1, e.ClipRectangle.Y - 1, e.ClipRectangle.Width + 2, e.ClipRectangle.Height + 2);
using (var controlImage = new Bitmap(e.ClipRectangle.Width, e.ClipRectangle.Height, PixelFormat.Format32bppArgb))
{
using (Graphics ctrlG = Graphics.FromImage(controlImage))
{
/* Render the control. We use ButtonRenderer and not ComboBoxRenderer because we want the control to appear with the DropDownList style. */
if (this.DroppedDown) { ButtonRenderer.DrawButton(ctrlG, topTextRectangle, PushButtonState.Pressed); }
else if (this.itemIsHot) { ButtonRenderer.DrawButton(ctrlG, topTextRectangle, PushButtonState.Hot); }
else { ButtonRenderer.DrawButton(ctrlG, topTextRectangle, PushButtonState.Normal); }
// Draw item, if any has been selected
if (this.comittedSelection != null)
{
ImageComboBox.DrawComboBoxItem(ctrlG, this.GetDistplayText(this.comittedSelection), this.GetImage(this.comittedSelection)
, topTextRectangle, this.Height, this.Width - ImageComboBox.arrowSize.Width, this.ImageMargin, Brushes.Black, this.SelectedItemFont);
}
/* Now we need to draw the arrow. If we use ComboBoxRenderer for this job, it will display a distinct border around the dropDownArrow and we don't want that. As an alternative we define the area where the arrow should be drawn, and then procede to draw it. */
var dropDownButtonArea = new RectangleF(topTextRectangle.X + topTextRectangle.Width - (ImageComboBox.arrowSize.Width
+ this.dropDownArrow.Image.Width) / 2.0F, topTextRectangle.Y + topTextRectangle.Height - (topTextRectangle.Height
+ this.dropDownArrow.Image.Height) / 2.0F, this.dropDownArrow.Image.Width, this.dropDownArrow.Image.Height);
ctrlG.DrawImage(this.dropDownArrow.Image, dropDownButtonArea);
}
if (this.Enabled) { e.Graphics.DrawImage(controlImage, 0, 0); }
else { ControlPaint.DrawImageDisabled(e.Graphics, controlImage, 0, 0, Color.Transparent); }
}
}
}
internal struct ImgHolder
{
internal Image Image
{
get
{
return this._image ?? new Bitmap(1, 1); ;
}
}
private Image _image;
internal ImgHolder(Bitmap data)
{
_image = data;
}
internal ImgHolder(Image data)
{
_image = data;
}
internal static ImgHolder Create(Image data)
{
return new ImgHolder(data);
}
internal static ImgHolder Create(Bitmap data)
{
return new ImgHolder(data);
}
}
}