I set a PropertyGrid's SelectedObject to an object with a Bitmap property:
public class Obj
{
private Bitmap myimage;
[Category("Main Category")]
[Description("Your favorite image")]
public Bitmap MyImage
{
get { return myimage; }
set
{
myimage = value;
}
}
}
This automatically shows the property in the PropertyGrid with a little button that the user can click on to select the image. How can I get the file path of this image?
(Also, is there any way I can override the dialog box that pops up and put my own one in? I know this is a separate question so I'll probably post it separately.)
How can I get the file path of this image?
Also, is there any way I can override the dialog box that pops up and put my own one in?
To achieve the both one way would be to create a new UITypeEditor:
public class BitmapLocationEditor : UITypeEditor
{
}
I overrided the GetEditStyle, EditValue, GetPaintvalueSupported and PaintValue methods.
// Displays an ellipsis (...) button to start a modal dialog box
public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context)
{
return UITypeEditorEditStyle.Modal;
}
// Edits the value of the specified object using the editor style indicated by the GetEditStyle method.
public override object EditValue(ITypeDescriptorContext context,
IServiceProvider provider,
object value)
{
// Show the dialog we use to open the file.
// You could use a custom one at this point to provide the file path and the image.
using (OpenFileDialog openFileDialog = new OpenFileDialog())
{
openFileDialog.Filter = "Image files (*.bmp | *.bmp;";
if (openFileDialog.ShowDialog() == DialogResult.OK)
{
Bitmap bitmap = new Bitmap(openFileDialog.FileName);
bitmap.SetPath(openFileDialog.FileName);
return bitmap;
}
}
return value;
}
// Indicates whether the specified context supports painting
// a representation of an object's value within the specified context.
public override bool GetPaintValueSupported(ITypeDescriptorContext context)
{
return true;
}
// Paints a representation of the value of an object using the specified PaintValueEventArgs.
public override void PaintValue(PaintValueEventArgs e)
{
if (e.Value != null)
{
// Get image
Bitmap bmp = (Bitmap)e.Value;
// This rectangle indicates the area of the Properties window to draw a representation of the value within.
Rectangle destRect = e.Bounds;
// Optional to set the default transparent color transparent for this image.
bmp.MakeTransparent();
// Draw image
e.Graphics.DrawImage(bmp, destRect);
}
}
Your Obj class should remain the same, except add an attribute to take advantage of our new BitmapLocationEditor class:
public class Obj
{
private Bitmap myImage;
[Category("Main Category")]
[Description("Your favorite image")]
[Editor(typeof(BitmapLocationEditor), typeof(UITypeEditor))]
public Bitmap MyImage
{
get { return myImage; }
set
{
myImage = value;
}
}
}
The SetPath method used in EditValue method is just an extension method to set the Bitmap.Tag property. You can use later the GetPath method to retrieve the actual path of your image file.
public static class BitmapExtension
{
public static void SetPath(this Bitmap bitmap, string path)
{
bitmap.Tag = path;
}
public static string GetPath(this Bitmap bitmap)
{
return bitmap.Tag as string;
}
}
Article about PropertyGrid
Related
I just wrote thsi code to have an access to images
private Bitmap[] hi = { HangmanUrdu.Properties.Resources._4, HangmanUrdu.Properties.Resources._5, HangmanUrdu.Properties.Resources._6, HangmanUrdu.Properties.Resources._7, HangmanUrdu.Properties.Resources._8, HangmanUrdu.Properties.Resources._9, HangmanUrdu.Properties.Resources._10 };
but when i want to increment the index and get these images in my picture box
// wg is just a counter;
pictureBox1.Image = hi { wg}; i
t throws me an error saying
cannot implicitly convert Bitmap to images
I also tried to change my array from bitmap to image but then it shows me error that cannot convert Images to Images.
Create a List<Bitmap> - as a Field, here - or any other type that fits the design (a class property, for example).
Fill the List<Bitmap> in a Form's constructor with the Bitmap objects needed in that context, creating a new Bitmap from the resource object:
private List<Bitmap> hi = null;
public Form1()
{
InitializeComponent();
this.hi = new List<Bitmap>()
{
new Bitmap(Properties.Resources._4),
new Bitmap(Properties.Resources._5)
};
}
The assign a Bitmap to a control's Image property when you need to:
pictureBox1.Image = hi[1];
You could also build a specialized class that hold these references, so you can access them with different naming conventions.
For example:
private List<BitmapResource> BitmapResources = null;
public Form1()
{
InitializeComponent();
this.BitmapResources = new List<BitmapResource>()
{
new BitmapResource(new Bitmap(Properties.Resources._4), "Logo"),
new BitmapResource(new Bitmap(Properties.Resources._5), "Watermark")
};
}
internal class BitmapResource
{
public BitmapResource(Bitmap bitmap, string imageName)
{
this.Image = bitmap;
this.Name = imageName;
}
public Bitmap Image { get; private set; }
public string Name { get; private set; }
}
Then, when needed:
By index:
pictureBox1.Image = BitmapResources[0].Image;
By name (simplified):
pictureBox1.Image = BitmapResources.FirstOrDefault(res => res.Name == "Logo").Image;
Goal: add an image above an UIAlertController's Title label by subclassing UIAlertController and adding new line characters, \n, to the title string to make space for the UIImageView
Desire
Current
As one can see, able to add the image to the UIAlertController successfully but the image is not being spacing/placed above the Title. It appears to be adding to the center of the alert. How to space the image correctly above the UIAlertController title?
Current code:
namespace XamarinFormsApp1.Extensions
{
public class AlertController : UIAlertController
{
private string originalTitle;
private string spaceAdjustedTitle;
private UIImageView imageView = null;
private CoreGraphics.CGSize previousImgViewSize
= CoreGraphics.CGSize.Empty;
public override UIAlertControllerStyle PreferredStyle
{
get
{
return UIAlertControllerStyle.Alert;
}
}
public override string Title
{
get
{
return originalTitle;
}
set
{
if (Title != spaceAdjustedTitle ||
string.IsNullOrEmpty(Title) ||
string.IsNullOrEmpty(spaceAdjustedTitle))
{
originalTitle = value;
}
}
}
public void setTitleImage(UIImage image)
{
if (this.imageView == null)
{
UIImageView imageView = new UIImageView(image);
this.View.AddSubview(imageView);
this.imageView = imageView;
return;
}
imageView.Image = image;
}
public override void ViewDidLayoutSubviews()
{
if (imageView == null)
{
base.ViewDidLayoutSubviews();
return;
}
// Adjust title if image size has changed
if (previousImgViewSize != imageView.Bounds.Size)
{
previousImgViewSize = imageView.Bounds.Size;
adjustTitle(imageView);
}
// Position `imageView`
var linesCount = newLinesCount(imageView);
var padding = Constants.Padding(PreferredStyle);
var x = View.Bounds.Width / 2.0;
var y = padding + linesCount * lineHeight / 2.0;
CoreGraphics.CGPoint cgPoint = imageView.Center;
cgPoint.X = (nfloat)x;
cgPoint.Y = (nfloat)y;
imageView.Center = cgPoint;
base.ViewDidLayoutSubviews();
}
private void adjustTitle(UIImageView imageView)
{
var linesCount = (int)newLinesCount(imageView);
var lines = Enumerable
.Range(1, linesCount)
.Select(i => "\n")
.Aggregate((c, n) => $"{c}{n}");
spaceAdjustedTitle = lines + (originalTitle ?? "");
Title = spaceAdjustedTitle;
}
private double newLinesCount(UIImageView imageView)
{
return Math.Ceiling(
imageView.Bounds.Height / lineHeight);
}
private float lineHeight
{
get
{
UIFontTextStyle style = this.PreferredStyle
== UIAlertControllerStyle.Alert
? UIFontTextStyle.Headline
: UIFontTextStyle.Callout;
return (float)UIFont
.GetPreferredFontForTextStyle(style)
.PointSize;
}
}
struct Constants
{
static float paddingAlert = 22;
static float paddingSheet = 11;
public static float Padding(UIAlertControllerStyle style)
{
return style == UIAlertControllerStyle.Alert
? Constants.paddingAlert
: Constants.paddingSheet;
}
}
}
}
Note: Credit to #stringCode for image and swift solution, see.
UIAlertViewController is not meant to be subclassed.
An extract from the documentation says:
You could still get the UI you desire by using a UIViewController with transparency on the View and a subview with the layout you desire.
You would need to also set these two properties: ModalTransitionStyle and ModalPresentationStyle to UIModalTransitionStyle.CrossDissolve and UIModalPresentationStyle.OverCurrentContext respectively if you want your custom UIAlertController to behaves the same as a UIAlertController
Update:
This is what I meant you could do:
In the Main.Storyboard drop a UIViewController and update the design as you wish. Following the image you posted above I created the UI as seen below:
That's an Image, 2 UILabels for the Title and Message and 3 buttons for the 3 different actions (Default, Destroy, Cancel). All these controls are inside a UIView with White background. For the example I called it ContentView
Adding the 3 button on the UI seems to be the easiest way to work with this and then hide/show them when you are about to present your alert. You could also create the buttons on the fly based on the actions you wanna show. This is up to you.
Create a ViewController Class, I called it NiceAlertController, and assign it to the ViewController in the Storyboard. Also, make sure to create back properties (Outlets) for all the UIControls (Label, Button, Image, etc) so you can access it from the ViewController class.
Here more information about how to work with iOS Storyboard on the designer
Now in your class you will need to add the code to make it work.
In your class to make the view transparent you will need to add this to your ViewDidLoad method:
public override void ViewDidLoad()
{
base.ViewDidLoad();
this.View.BackgroundColor = UIColor.Clear.ColorWithAlpha(0.2f);
this.View.Opaque = false;
}
Also, we could mimic the way UIAlertControllers are created and create our method like that one:
public static NiceAlertController Create(string title, string message, UIImage image = null)
{
//New instance of your ViewController UI
var storyboard = UIStoryboard.FromName("Main", NSBundle.MainBundle);
var alertController = storyboard.InstantiateViewController(nameof(NiceAlertController)) as NiceAlertController;
//Using the same transition and presentation style as the UIAlertViewController
alertController.ModalTransitionStyle = UIModalTransitionStyle.CrossDissolve;
alertController.ModalPresentationStyle = UIModalPresentationStyle.OverCurrentContext;
//This assumes your properties are called Title, Message and Image
alertController.Title = title;
alertController.Message = message;
alertController.Image = image;
return alertController;
}
The 3 properties used above (Title, Message and Image) looks like this:
public new string Title { get; private set; }
public string Message { get; private set; }
public UIImage Image { get; private set; }
Why these properties? because by the time you create the class the Controls on the view are not yet available. They will be available only after the View is loaded. This is why we will need to add other changes like the one below.
Here we are setting the values to the Controls on the UI
public override void ViewWillAppear(bool animated)
{
base.ViewWillAppear(animated);
this.titleLabel.Text = Title;
this.messageLabel.Text = Message;
//If you don't set an image while Create, it will use the default image you set on the Designer.
if (Image != null)
{
this.imageView.Image = Image;
}
}
Now from any other ViewController you can call this ViewController as you would call an AlertViewController:
private void ShowMyAlertController()
{
var alert = NiceAlertController.Create("Hello", "My nice alert controller");
this.PresentViewController(alert, true, null);
}
And it should look like this:
To handle the Actions (What to do when the buttons are tapped) you could create specific methods like:
public void AddDefaultAction(string title, Action action)
{
//Logic here
}
public void AddCancelAction(string title, Action action)
{
//Logic here
}
public void AddDestructiveAction(string title, Action action)
{
//Logic here
}
Hope this gives you the idea of how to create custom UIViewcontroller and make it look like a UIAlertViewController.
Hope this helps.-
I doing some items for my game make the development easier, I wish to add custom icons to my items, which are ScriptableObjects, how can I do this? I know the trick of put the icon on Gizmo folder with the same name as the script, but the icons should be different for different items which have the same script
You can add a RawImage field to your scriptable object.
[SerializeField] RawImage imageIcon;
Edit to answer your additional question:
Create the new object with your scriptable object
Click on the newly created object
View inspector and click the icon:
Click Other
Pick your image.
Additionally you can check out this asset package and create icons based on the data: https://assetstore.unity.com/packages/tools/gui/asseticons-100547
Create Field "icon" in your ScriptableObject sub class;
Create Custom Editor for your ScriptableObject sub class;
Create Target property in editor
public StateMachine Target {
get {
return this.target as StateMachine;
}
}
override this method
public override Texture2D RenderStaticPreview(string assetPath, UnityEngine.Object[] subAssets, int width, int height) {
Texture2D newIcon = new Texture2D(width, height);
if (Target.icon != null) {
EditorUtility.CopySerialized(Target.icon, newIcon);
return newIcon;
} else {
Texture2D defaultCustomIcon = AssetDatabase.LoadAssetAtPath("Assets/WinxProduction/Editor/Editor Default Resources/StateMachine Icon.png", typeof(Texture2D)) as Texture2D;
if (defaultCustomIcon != null) {
EditorUtility.CopySerialized(defaultCustomIcon, newIcon);
Target.icon=newIcon;
AssetDatabase.AddObjectToAsset(newIcon,Target);
EditorUtility.SetDirty(Target);
return newIcon;
}
}
return base.RenderStaticPreview(assetPath, subAssets, width, height);
}
First time when Unity Loads will use default Icon and save for all custom sub class ScriptableObjects. If you want to have different icons then default you would create some function like this:
protected void OnClickBrowseForNewIcon() {
string path=EditorUtility.OpenFilePanelWithFilters("Select icon","Assets",new string[]{"Icon files", "png",});
if(!string.IsNullOrEmpty(path)){
Target.icon=AssetDatabase.LoadAssetAtPath(AssetDatabaseUtility.AbsoluteUrlToAssets(path));
EditorUtility.SetDirty(Target);
}
}
Relative to absolute
public static class AssetDatabaseUtility
{
public static string AbsoluteUrlToAssets(string absoluteUrl)
{
Uri fullPath = new Uri (absoluteUrl, UriKind.Absolute);
Uri relRoot = new Uri (Application.dataPath, UriKind.Absolute);
return relRoot.MakeRelativeUri (fullPath).ToString ();
}
}
Instead of setting image using 'background' property, I would like to draw the image using Graphics class on a panel. How can I do it in C#.Net?
you can try following piece of code.
public class ImagePanel:Panel
{
private Image image;
public Image Image
{
get { return image; }
set
{
image = value;
Refresh();
}
}
protected override void OnPaint(PaintEventArgs e)
{
if(Image!=null)
{
e.Graphics.DrawImage(this.Image,Point.Empty);
}
base.OnPaint(e);
}
}
Make use of System.Drawing.Graphics class to draw the things.
Details : http://msdn.microsoft.com/en-us/library/system.drawing.graphics.aspx related to drawing
example : http://www.techotopia.com/index.php/Drawing_Graphics_in_C_Sharp
Morning all,
I've created a custom control with an image property. That image property is a get/set to a private Image variable.
Can anyone tell me how I enable that get/set to clear the property from the designer?
I.e. if I add an image to a standard PictureBox, I can hit Del to clear the image from the PictureBox. How can I replicate this behaviour on my own custom control?
At the simplest level, DefaultValueAttribute should do the job:
private Bitmap bmp;
[DefaultValue(null)]
public Bitmap Bar {
get { return bmp; }
set { bmp = value; }
}
For more complex scenarios, you might want to try adding a Reset method; for example:
using System;
using System.Drawing;
using System.Windows.Forms;
class Foo {
private Bitmap bmp;
public Bitmap Bar {
get { return bmp; }
set { bmp = value; }
}
private void ResetBar() { bmp = null; }
private bool ShouldSerializeBar() { return bmp != null; }
}
static class Program {
[STAThread]
static void Main() {
Application.EnableVisualStyles();
Form form = new Form();
PropertyGrid grid = new PropertyGrid();
grid.Dock = DockStyle.Fill;
grid.SelectedObject = new Foo();
form.Controls.Add(grid);
Application.Run(form);
}
}