Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 6 years ago.
Improve this question
How to Display Controls(e.g. RadioButton, Button etc.) when using OnPaint Method in C#? It is possible to create custom controls in constructor, but I need to use common controls of C#? Please suggest.
I'm trying to display common controls but no use.
Please find below code.
private void PaintPanelOrButton(object sender, PaintEventArgs e)
{
Point pt1 = new Point(radioButton1.Left + (radioButton1.Width / 2), radioButton1.Top + (radioButton1.Height / 2));
Point pt2 = new Point(radioButton2.Left + (radioButton2.Width / 2), radioButton2.Top + (radioButton2.Height / 2));
if (sender is Button)
{
Button btn = (Button)sender;
pt1.X -= btn.Left;
pt1.Y -= btn.Top;
pt2.X -= btn.Left;
pt2.Y -= btn.Top;
}
e.Graphics.DrawLine(new Pen(Color.Red, 4.0F), pt1, pt2);
}
public GlassForm()
{
TextBox t = new TextBox();
t.Left = 1000;
t.Top = 900;
t.Name = "txt1";
this.Controls.Add(t);
this.SuspendLayout();
Button buttonOK = new Button();
buttonOK.Location = new Point(10, 10);
buttonOK.Size = new Size(75, 25);
buttonOK.Text = "OK";
Button buttonCancel = new Button();
buttonCancel.Location = new Point(90, 10);
buttonCancel.Size = new Size(75, 25);
buttonCancel.Text = "Cancel";
this.Controls.AddRange(new Control[] { buttonOK, buttonCancel });
this.ResumeLayout();
this.TransparencyKey = transparentColor;
}
protected override void OnPaint(PaintEventArgs e)
{
try
{
base.OnPaint(e);
Rectangle r1 = new Rectangle(10, 10, 50, 50);
Rectangle r2 = new Rectangle(40, 40, 50, 50);
Region r = new Region(r1);
r.Union(r2);
GraphicsPath path = new GraphicsPath(new Point[] {new Point(45, 45),
new Point(145, 55),
new Point(200, 150),
new Point(75, 150),
new Point(45, 45)
}, new byte[] { (byte)PathPointType.Start,
(byte)PathPointType.Bezier,
(byte)PathPointType.Bezier,
(byte)PathPointType.Bezier,
(byte)PathPointType.Line
});
r.Union(path);
using (Brush transparentBrush = new SolidBrush(transparentColor))
{
try
{
BlurBehindWindowEnabled = true;
ExtendFrameEnabled = false;
if (ExtendFrameEnabled)
{
var glassMargins = this.GlassMargins;
NativeMethods.DwmExtendFrameIntoClientArea(this.Handle, ref glassMargins);
marginRegion = new Region(new Rectangle(10, 10, 600, 100));
e.Graphics.FillRegion(transparentBrush, marginRegion);
}
else
{
var glassMargins = new NativeMethods.MARGINS(-1);
NativeMethods.DwmExtendFrameIntoClientArea(this.Handle,
ref glassMargins);
}
if (BlurBehindWindowEnabled)
{
ResetDwmBlurBehind(true, e.Graphics);
e.Graphics.FillRegion(transparentBrush, r);
}
else
{
ResetDwmBlurBehind(false, null);
}
}
catch (Exception ex)
{
lbAeroGlassStyleSupported.Text = "Error";
demoForm.Show();
}
}
this.ShowInTaskbar = false;
this.TopMost = true;
this.WindowState = FormWindowState.Maximized;
clsTaskbar.Show();
}
catch (Exception ex)
{
}
}
}
If I understand correctly, you want something that look exactly like "normal" controls, but are actually just images.
If so, you can use ControlPaint class: it has many methods to draw buttons, checkBoxex, radiobuttons, etc.
Most of them require a Graphics parameter, so you can easily call them from within your OnPaint event handler
If you want to draw the controls by yourself, you can search for a xxxRenderer class within the .net framework. By using these you can draw check boxes, radio buttons, normal buttons, etc.
But be aware that at is some quite of work, to reproduce the same behavior as the built-in controls (clicking, hovering, focus, keyboard shortcuts, etc.).
Related
I'm drawing a custom toolstrip using ToolStripProfessionalRender and editing the OnRenderItemText event as follows:
protected override void OnRenderItemText(ToolStripItemTextRenderEventArgs e)
{
e.Item.ForeColor = Clr.White;
e.Item.TextAlign = ContentAlignment.MiddleLeft;
e.Item.Alignment = ToolStripItemAlignment.Left;
base.OnRenderItemText(e);
if (e.Item.GetType() == typeof(ToolStripDropDownButton))
{
ToolStripDropDownButton tsmi = (ToolStripDropDownButton)e.Item;
if (tsmi.HasDropDownItems && tsmi.OwnerItem == null)
{
Rectangle bounds = tsmi.Bounds;
bounds.X = bounds.Right - 25;
bounds.Width = 25;
bounds.Y = 10;
// Draw the corner
Graphics G = e.Graphics;
SolidBrush brushw = new SolidBrush(Color.FromArgb(70,70,70));
Point[] points =
{
new Point(bounds.Right - 3, bounds.Height - 11), // point top right
new Point(bounds.Right - 3, bounds.Bottom - 14), // point bottom right
new Point(bounds.Right - 10, bounds.Bottom - 14) // point bottom left
};
G.FillPolygon(brushw, points);
}
}
}
and basically the output i'm trying to obtain is the following:
So drawing a little triangle on the bottom right corner when i got a ToolStripDropDownButton. The problem is that the little triangle is drawn only first item.
To end up the question i draw this toolstrip dynamically using a function that adds a dropdownbutton at each call.
ToolStripDropDownButton m_Item = new ToolStripDropDownButton(text, image);
m_Item.ImageAlign = ContentAlignment.MiddleCenter;
m_Item.ImageScaling = ToolStripItemImageScaling.None;
m_Item.Name = name;
m_Item.ForeColor = Color.White;
m_Item.BackColor = Color.FromArgb(95, 95, 95);
m_Item.Padding = new Padding(5);
m_Item.ShowDropDownArrow = false;
m_Item.Paint += new PaintEventHandler(this.PaintButtonBorder);
if (tabPage != null)
m_Item.Click += (sender, e) => AddClickTab(sender, e, tabPage);
((ToolStripDropDownMenu)m_Item.DropDown).ShowImageMargin = false;
((ToolStripDropDownMenu)m_Item.DropDown).ShowCheckMargin = false;
((ToolStripDropDownMenu)m_Item.DropDown).Cursor = Cursors.Hand;
toolStrip1.Items.Add(m_Item);
if (SubItems != null)
{
for(int i = 0; i < SubItems.Length; i++)
{
object[] subitem = (object[])SubItems[i];
FnAddToolStripMenuItem(
subitem[0].ToString(),
subitem[1].ToString(),
(Bitmap)subitem[2],
m_Item,
(TabPage)subitem[3]
);
}
}
Am i missing some "new" maybe?
Override the OnRenderItemText method only to draw the text part as it says, and/or to set the default properties used when rendering the text. To change the look and the shape of the arrows of the dropdown items, override the OnRenderArrow method.
Example
using System.Drawing;
using System.Drawing.Drawing2D;
protected override void OnRenderArrow(ToolStripArrowRenderEventArgs e)
{
// Optional: to be the default color of the arrows.
e.ArrowColor = Color.FromArgb(70, 70, 70);
if (e.Item is ToolStripDropDownButton item && item.OwnerItem == null)
{
var g = e.Graphics;
var r = new Rectangle(item.Bounds.Width - 10, item.Bounds.Height - 10, 8, 8);
g.SmoothingMode = SmoothingMode.AntiAlias;
g.PixelOffsetMode = PixelOffsetMode.Half;
using (var br = new SolidBrush(e.ArrowColor))
g.FillPolygon(br, new[]
{
new Point(r.Left, r.Bottom),
new Point(r.Right, r.Top),
new Point(r.Right, r.Bottom)
});
g.SmoothingMode = SmoothingMode.None;
g.PixelOffsetMode = PixelOffsetMode.Default;
}
else
base.OnRenderArrow(e);
}
protected override void OnRenderItemText(ToolStripItemTextRenderEventArgs e)
{
e.Item.ForeColor = Color.White;
e.Item.TextAlign = ContentAlignment.MiddleLeft;
e.Item.Alignment = ToolStripItemAlignment.Left;
base.OnRenderItemText(e);
}
Make sure to enable the ShowDropDownArrow property of the dropdown buttons. So comment this m_Item.ShowDropDownArrow = false;.
If you are also interested to change the color according to the current state of the dropdown button (Selected, Pressed), then you can do for example:
using (var br = new SolidBrush(item.Selected
? Color.FromArgb(150, 150, 150) : item.Pressed
? Color.FromArgb(100, 100, 100) :
Color.FromArgb(70, 70, 70)))
//...
I am writing the app, and a part of it is open textbox. When the textbox is opening I want to dark background.
I have looked the solution and found it here:
Creating a dark background when a new form appears
But, it does not work for me correctly.
Here is my code:
private void App_Load(object sender, EventArgs e)
{
this.Text = "TestApp";
this.Size = new Size(350, 250);
this.BackColor = Color.DarkGray;
this.Location = new Point(50, 50);
this.MaximizeBox = false;
TextBox.BackColor = Color.WhiteSmoke;
TextBox.Multiline = true;
TextBox.Size = new Size(200, 90);
Button.Text = "Search";
Bitmap bmp = new Bitmap(this.ClientRectangle.Width, this.ClientRectangle.Height);
using (Graphics G = Graphics.FromImage(bmp))
{
G.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceOver;
G.CopyFromScreen(this.PointToScreen(new Point(0, 0)), new Point(0, 0), this.ClientRectangle.Size);
double percent = 0.60;
Color darken = Color.FromArgb((int)(255 * percent), Color.Black);
using (Brush brsh = new SolidBrush(darken))
{
G.FillRectangle(brsh, this.ClientRectangle);
}
}
using (Panel p = new Panel())
{
p.Location = new Point(0, 0);
p.Size = this.ClientRectangle.Size;
p.BackgroundImage = bmp;
this.Controls.Add(p);
p.BringToFront();
// display your dialog somehow:
Form frm = new Form();
frm.StartPosition = FormStartPosition.Manual;
frm.ShowDialog(this);
}
}
I receive this:
Maybe, someone can point me out where is my mistake?
EDIT: I have found the solution, the question was not clear enough.
When the textbox is opening I want to dark background.
So you want the textBox to be dark, not the complete form?
Almost always when you think you have to do some painting yourself, think again. It is seldom necessary do to paint. Only do this, if you don't have any standard options.
Just set Property BackGround of the text box. Use visual studio designer to do this.
If you don't want to do this using the designer, do this in the constructor after InitializeComponent:
public MyForm()
{
InitializeComponent();
// text box dark background:
this.textBox1.BackColor = Color.Black;
}
If you want the complete form to be black, again use visual studio designer, or add:
InitializeComponent();
this.BackColor = Color.Black;
As the title says i have buttons being created as anonymous objects after an event occurs(the click of another button).What i am trying to do is to make this buttons show a Messagebox when they are being clicked.I can't add this functionality to the buttons and i haven't found anything that solves my problem.Maybe it just can't happen this way.
Controls.Add(new Button { Size = new Size(50, 50),
Location = new Point(40 + i * 60, 100),
Text = i.ToString(),
BackColor = c,
//eventforshowingmessage()
});`
You could assign button to variable first, and then register event:
public AddButton()
{
var newButton = new Button { Text = "Button 1" };
newButton.Click += MyEventListener;
this.Controls.Add(newButton);
}
private void MyEventListener(object sender, EventArgs e)
{
var button = (Button)sender;
MessageBox.Show($"{button.Text} says: Hello, world");
}
I suggest a different syntax: Parent instead of Controls.Add:
for (int i ....) { // whatever loop
...
new Button {
Size = new Size(50, 50),
Location = new Point(40 + i * 60, 100),
Text = $"{i}", // May appear more readable than i.ToString()
BackColor = c,
Parent = this, // <- instead of this.Controls.Add
}.Click += eventforshowingmessage;
...
}
Demo: For instance, let's create 5 buttons and show up which button has been clicked:
for (int i = 0; i < 5; ++i) {
new Button {
Size = new Size(50, 50),
Location = new Point(40 + i * 60, 100),
Text = $"{i}",
BackColor = SystemColors.Control,
Parent = this,
}.Click += (ss, ee) => {
// Lambda: what shall we do on click
MessageBox.Show($"{(ss as Control).Text} clicked!");
};
}
While writing a PowerPoint add-in, I need to draw something on the screen on top of the slideshow.
I am able to draw lines and images, but they disappear almost immediately.
Example code:
private void Application_SlideShowBegin(PowerPoint.SlideShowWindow Wn)
{
using (var g = Graphics.FromHwnd((IntPtr)Wn.HWND))
{
g.DrawLine(new Pen(Color.Red, 10), new System.Drawing.Point(100, 100), new System.Drawing.Point(200, 300));
Image img = Properties.Resources.img;
g.DrawImageUnscaled(img, new Rectangle(250, 250, img.Width, img.Height));
}
}
Any idea how I can keep the drawn lines / images on the screen?
I should have seen it earlier - this is about refreshes. The code needs to be run a bit later, so if the paint is delayed, the drawing stays on the window.
The following example now works as expected:
private Timer _Ticker = new Timer();
private PowerPoint.SlideShowWindow _SlideshowWindow = null;
private void Application_SlideShowBegin(PowerPoint.SlideShowWindow Wn)
{
_SlideshowWindow = Wn;
_Ticker.Interval = 30;
_Ticker.AutoReset = true;
_Ticker.Elapsed += Ticker_Elapsed;
_Ticker.Enabled = true;
}
private void Ticker_Elapsed(object sender, ElapsedEventArgs e)
{
if (_Ticker.Enabled)
{
using (var g = Graphics.FromHwnd((IntPtr)_SlideshowWindow.HWND))
{
g.DrawLine(new Pen(Color.Red, 10), new System.Drawing.Point(100, 100), new System.Drawing.Point(200, 300));
Image img = Properties.Resources.img;
g.DrawImageUnscaled(img, new Rectangle(250, 250, img.Width, img.Height));
}
_Ticker.Enabled = false;
_Ticker.Elapsed -= Ticker_Elapsed;
}
}
Is it possible to create my own custom MessageBox where I would be able to add images instead of only strings?
I also wanted this feature, so I created WPFCustomMessageBox, a WPF clone of the native Windows/.NET MessageBox which supports extra features like custom button text.
WPFCustomMessageBox uses static methods just like the standard .NET MessageBox, so you can plug-and-play the new library without modifying any code. Most importantly, I designed this control so it looks identical to the original MessageBox.
I created this library because I wanted to use verbs for my MessageBox buttons to help users better understand the functionality of the buttons. With this library, you can offer your users button descriptions like Save/Don't Save or Eject Fuel Rods/Don't do it! rather than the standard OK/Cancel or Yes/No (although you can still use those too, if you like).
The WPFCustomMessageBox message boxes return standard .NET MessageBoxResults. It also offers the same features as the original MessageBox like MessageBoxIcons and custom message box captions.
WPFCustomMessageBox is open source, so you can grab the code or make improvements here: https://github.com/evanwon/WPFCustomMessageBox
You can add WPFCustomMessage to your project via NuGet: https://www.nuget.org/packages/WPFCustomMessageBox/
Here is the code needed to create your own message box:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace MyStuff
{
public class MyLabel : Label
{
public static Label Set(string Text = "", Font Font = null, Color ForeColor = new Color(), Color BackColor = new Color())
{
Label l = new Label();
l.Text = Text;
l.Font = (Font == null) ? new Font("Calibri", 12) : Font;
l.ForeColor = (ForeColor == new Color()) ? Color.Black : ForeColor;
l.BackColor = (BackColor == new Color()) ? SystemColors.Control : BackColor;
l.AutoSize = true;
return l;
}
}
public class MyButton : Button
{
public static Button Set(string Text = "", int Width = 102, int Height = 30, Font Font = null, Color ForeColor = new Color(), Color BackColor = new Color())
{
Button b = new Button();
b.Text = Text;
b.Width = Width;
b.Height = Height;
b.Font = (Font == null) ? new Font("Calibri", 12) : Font;
b.ForeColor = (ForeColor == new Color()) ? Color.Black : ForeColor;
b.BackColor = (BackColor == new Color()) ? SystemColors.Control : BackColor;
b.UseVisualStyleBackColor = (b.BackColor == SystemColors.Control);
return b;
}
}
public class MyImage : PictureBox
{
public static PictureBox Set(string ImagePath = null, int Width = 60, int Height = 60)
{
PictureBox i = new PictureBox();
if (ImagePath != null)
{
i.BackgroundImageLayout = ImageLayout.Zoom;
i.Location = new Point(9, 9);
i.Margin = new Padding(3, 3, 2, 3);
i.Size = new Size(Width, Height);
i.TabStop = false;
i.Visible = true;
i.BackgroundImage = Image.FromFile(ImagePath);
}
else
{
i.Visible = true;
i.Size = new Size(0, 0);
}
return i;
}
}
public partial class MyMessageBox : Form
{
private MyMessageBox()
{
this.panText = new FlowLayoutPanel();
this.panButtons = new FlowLayoutPanel();
this.SuspendLayout();
//
// panText
//
this.panText.Parent = this;
this.panText.AutoScroll = true;
this.panText.AutoSize = true;
this.panText.AutoSizeMode = AutoSizeMode.GrowAndShrink;
//this.panText.Location = new Point(90, 90);
this.panText.Margin = new Padding(0);
this.panText.MaximumSize = new Size(500, 300);
this.panText.MinimumSize = new Size(108, 50);
this.panText.Size = new Size(108, 50);
//
// panButtons
//
this.panButtons.AutoSize = true;
this.panButtons.AutoSizeMode = AutoSizeMode.GrowAndShrink;
this.panButtons.FlowDirection = FlowDirection.RightToLeft;
this.panButtons.Location = new Point(89, 89);
this.panButtons.Margin = new Padding(0);
this.panButtons.MaximumSize = new Size(580, 150);
this.panButtons.MinimumSize = new Size(108, 0);
this.panButtons.Size = new Size(108, 35);
//
// MyMessageBox
//
this.AutoScaleDimensions = new SizeF(8F, 19F);
this.AutoScaleMode = AutoScaleMode.Font;
this.ClientSize = new Size(206, 133);
this.Controls.Add(this.panButtons);
this.Controls.Add(this.panText);
this.Font = new Font("Calibri", 12F, FontStyle.Regular, GraphicsUnit.Point, ((byte)(0)));
this.FormBorderStyle = FormBorderStyle.FixedSingle;
this.Margin = new Padding(4);
this.MaximizeBox = false;
this.MinimizeBox = false;
this.MinimumSize = new Size(168, 132);
this.Name = "MyMessageBox";
this.ShowIcon = false;
this.ShowInTaskbar = false;
this.StartPosition = FormStartPosition.CenterScreen;
this.ResumeLayout(false);
this.PerformLayout();
}
public static string Show(Label Label, string Title = "", List<Button> Buttons = null, PictureBox Image = null)
{
List<Label> Labels = new List<Label>();
Labels.Add(Label);
return Show(Labels, Title, Buttons, Image);
}
public static string Show(string Label, string Title = "", List<Button> Buttons = null, PictureBox Image = null)
{
List<Label> Labels = new List<Label>();
Labels.Add(MyLabel.Set(Label));
return Show(Labels, Title, Buttons, Image);
}
public static string Show(List<Label> Labels = null, string Title = "", List<Button> Buttons = null, PictureBox Image = null)
{
if (Labels == null) Labels = new List<Label>();
if (Labels.Count == 0) Labels.Add(MyLabel.Set(""));
if (Buttons == null) Buttons = new List<Button>();
if (Buttons.Count == 0) Buttons.Add(MyButton.Set("OK"));
List<Button> buttons = new List<Button>(Buttons);
buttons.Reverse();
int ImageWidth = 0;
int ImageHeight = 0;
int LabelWidth = 0;
int LabelHeight = 0;
int ButtonWidth = 0;
int ButtonHeight = 0;
int TotalWidth = 0;
int TotalHeight = 0;
MyMessageBox mb = new MyMessageBox();
mb.Text = Title;
//Image
if (Image != null)
{
mb.Controls.Add(Image);
Image.MaximumSize = new Size(150, 300);
ImageWidth = Image.Width + Image.Margin.Horizontal;
ImageHeight = Image.Height + Image.Margin.Vertical;
}
//Labels
List<int> il = new List<int>();
mb.panText.Location = new Point(9 + ImageWidth, 9);
foreach (Label l in Labels)
{
mb.panText.Controls.Add(l);
l.Location = new Point(200, 50);
l.MaximumSize = new Size(480, 2000);
il.Add(l.Width);
}
Labels.ForEach(l => l.MinimumSize = new Size(Labels.Max(x => x.Width), 1));
mb.panText.Height = Labels.Sum(l => l.Height);
mb.panText.MinimumSize = new Size(Labels.Max(x => x.Width) + mb.ScrollBarWidth(Labels), ImageHeight);
mb.panText.MaximumSize = new Size(Labels.Max(x => x.Width) + mb.ScrollBarWidth(Labels), 300);
LabelWidth = mb.panText.Width;
LabelHeight = mb.panText.Height;
//Buttons
foreach (Button b in buttons)
{
mb.panButtons.Controls.Add(b);
b.Location = new Point(3, 3);
b.TabIndex = Buttons.FindIndex(i => i.Text == b.Text);
b.Click += new EventHandler(mb.Button_Click);
}
ButtonWidth = mb.panButtons.Width;
ButtonHeight = mb.panButtons.Height;
//Set Widths
if (ButtonWidth > ImageWidth + LabelWidth)
{
Labels.ForEach(l => l.MinimumSize = new Size(ButtonWidth - ImageWidth - mb.ScrollBarWidth(Labels), 1));
mb.panText.Height = Labels.Sum(l => l.Height);
mb.panText.MinimumSize = new Size(Labels.Max(x => x.Width) + mb.ScrollBarWidth(Labels), ImageHeight);
mb.panText.MaximumSize = new Size(Labels.Max(x => x.Width) + mb.ScrollBarWidth(Labels), 300);
LabelWidth = mb.panText.Width;
LabelHeight = mb.panText.Height;
}
TotalWidth = ImageWidth + LabelWidth;
//Set Height
TotalHeight = LabelHeight + ButtonHeight;
mb.panButtons.Location = new Point(TotalWidth - ButtonWidth + 9, mb.panText.Location.Y + mb.panText.Height);
mb.Size = new Size(TotalWidth + 25, TotalHeight + 47);
mb.ShowDialog();
return mb.Result;
}
private FlowLayoutPanel panText;
private FlowLayoutPanel panButtons;
private int ScrollBarWidth(List<Label> Labels)
{
return (Labels.Sum(l => l.Height) > 300) ? 23 : 6;
}
private void Button_Click(object sender, EventArgs e)
{
Result = ((Button)sender).Text;
Close();
}
private string Result = "";
}
}
This took me 2 days to write. I hope it works for anyone that needs it.
I've implemented a WPF MessageBox fully customizable via standard WPF control templates:
http://blogs.microsoft.co.il/blogs/arik/archive/2011/05/26/a-customizable-wpf-messagebox.aspx
Features
The class WPFMessageBox has the exact same interface as the current WPF MessageBox class.
Implemented as a custom control, thus fully customizable via standard WPF control templates.
Has a default control template which looks like the standard MessageBox.
Supports all the common types of message boxes: Error, Warning, Question and Information.
Has the same “Beep” sounds as when opening a standard MessageBox.
Supports the same behavior when pressing the Escape button as the standard MessageBox.
Provides the same system menu as the standard MessageBox, including disabling the Close button when the message box is in Yes-No mode.
Handles right-aligned and right-to-left operating systems, same as the standard MessageBox.
Provides support for setting the owner window as a WinForms Form control.
Sure. I've done it by subclassing System.Windows.Window and adding the capacity to show various kinds of content (images, text and controls), and then calling ShowDialog() on that Window:
public partial class MyMessageBox : Window
{
// perhaps a helper method here
public static bool? Show(String message, BitmapImage image)
{
// NOTE: Message and Image are fields created in the XAML markup
MyMessageBox msgBox = new MyMessageBox() { Message.Text = message, Image.Source = image };
return msgBox.ShowDialog();
}
}
In the XAML, something like this:
<Window>
<DockPanel>
<Image Name="Image" DockPanel.Dock="Left" />
<TextBlock Name="Message" />
</DockPanel>
</Window>
I was in need like you and I have found this source and modified the way I wanted and you could get the most benefit out of it
here is the link
this is what it looks like by default: