Related
There is a "Custom Trackbar", which can take negative and positive values. If you set Min = -50, Max = 100, the slider moves outside the scrollbar. I need it to behave in the same way as "Standard Trackbar" (it should not go beyond the scrollbar boundaries). How to do it?
The screenshot shows 2 Trackbars for both I set (Minimum = -50, Maximum = 100, Value = -50), but after building the project I got the following picture:
If we set (Minimum = 0, Maximum = 100, Value = 25), we get the following:
[Code Custom Trackbar]
[DefaultEvent("ValueChanged")]
public class HandyHTrackbarWorked : Control {
#region Установка начальных параметров
public HandyHTrackbarWorked() {
SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.UserPaint
| ControlStyles.AllPaintingInWmPaint | ControlStyles.ResizeRedraw, true); UpdateStyles();
Size = new Size(250, 12);
ThumbSize = new Size(ThumbRect.Width = 15, ThumbRect.Height = 12);
}
#endregion
#region Основные свойства
private double _value;
public double Value {
get { return _value; }
set {
_value = value;
//if (_value < Minimum) { _value = Minimum; }
//if (_value > Maximum) { _value = Maximum; }
OnScroll(); Refresh();
}
}
private double minimum;
public double Minimum {
get { return minimum; }
set { minimum = value; Invalidate(); }
}
private double maximum = 100;
public double Maximum {
get { return maximum; }
set { maximum = value; Invalidate(); }
}
private double smallStep = 1;
public double SmallStep {
get { return smallStep; }
set {
smallStep = (value > 0) ? value : 1;
}
}
#endregion
#region Свойства, отвечающие за оформление
[Description("Размер ползунка")]
private Size thumbSize;
public Size ThumbSize {
get { return thumbSize; }
set {
thumbSize = value;
//if (thumbSize.Width % 2 == 0 && thumbSize.Width > 0) thumbSize.Width += 1;
Invalidate();
}
}
[Description("Цвет ползунка")]
private Color thumbBackColor = Color.FromArgb(255, 255, 255);
public Color ThumbBackColor {
get { return thumbBackColor; }
set { thumbBackColor = value; Invalidate(); }
}
private Color trackBackColor = Color.Transparent;
public Color TrackBackColor {
get { return trackBackColor; }
set { trackBackColor = value; Invalidate(); }
}
private Color trackBorderColor = Color.FromArgb(221, 0, 49);
public Color TrackBorderColor {
get { return trackBorderColor; }
set { trackBorderColor = value; Invalidate(); }
}
private Color trackBorderColor2 = Color.FromArgb(64, 64, 64);
public Color TrackBorderColor2 {
get { return trackBorderColor2; }
set { trackBorderColor2 = value; Invalidate(); }
}
[Description("Толщина")]
private int trackThickness = 2;
public int TrackThickness {
get { return trackThickness; }
set { trackThickness = value; Invalidate(); }
}
public new Padding Padding {
get { return base.Padding; }
set { base.Padding = value; Invalidate(); }
}
public Rectangle ThumbRect;
#endregion
#region Основные события
public event EventHandler ValueChanged;
#endregion
#region Обработчики событий
private Point startMouseClickPosition;
private Point currentMousePosition;
protected override void OnCreateControl() {
base.OnCreateControl();
this.MouseDown += (sender, e) => {
// When clicking on the ScrollBar, center the Thumb relative to the mouse cursor
if (!ThumbRect.Contains(e.Location)) {
MoveThumb(e, false);
}
// When clicking on Thumb, determine the startMouseClickPosition
if (ThumbRect.Contains(e.Location)) {
startMouseClickPosition.X = e.X - ThumbRect.Left; // OR ... - ThumbRect.X
ThumbBackColor = Color.Green;
}
};
this.MouseMove += (sender, e) => {
ThumbBackColor = ThumbRect.Contains(e.Location)
? ThumbBackColor = Color.Orange : ThumbBackColor = Color.Gray;
if (e.Button == MouseButtons.Left) {
ThumbBackColor = Color.Green; MoveThumb(e);
}
};
this.MouseLeave += (sender, e) => { ThumbBackColor = Color.Gray; };
}
int PaddingLR = 10;
// padding(left/right) must be the same,
// if the orientation of the scroll bar is HORIZONTAL
private void MoveThumb(MouseEventArgs e, bool useStartMouseClickPosition = true) {
double newValue;
if (useStartMouseClickPosition) {
currentMousePosition.X = e.X - startMouseClickPosition.X;
// works correctly
newValue = Maximum * (currentMousePosition.X - (ThumbSize.Width / 2) + (ThumbSize.Width / 2) - PaddingLR)
/ (Width - ThumbSize.Width - PaddingLR * 2);
} else {
newValue = Maximum * (e.X - ThumbSize.Width / 2 - PaddingLR)
/ (Width - ThumbSize.Width - PaddingLR * 2);
}
// does NOT work correctly (although the calculation result is the same)
//double newValue = Maximum * (newThumbLeft + (ThumbSize.Width / 2) - PaddingLR) /
// (Width - ThumbSize.Width - PaddingLR * 2);
Value = Math.Max(0, Math.Min(Maximum, newValue));
}
public void OnScroll() {
ValueChanged?.Invoke(this, EventArgs.Empty);
}
#endregion
#region Отрисовка элементов управления
protected override void OnPaint(PaintEventArgs e) {
ThumbRect = new Rectangle(
Convert.ToInt32(Value * (Width - ThumbSize.Width - Padding.Left * 2) / Maximum + Padding.Right),
0 + Padding.Top,
ThumbSize.Width, // fixed slider width
Height - Padding.Bottom - Padding.Top // dynamic slider height
// (example) Height - 4, means to move the slider by 2 px above and below
);
// Filling the scroll bar
using (SolidBrush brush = new SolidBrush(TrackBackColor)) {
e.Graphics.FillRectangle(brush, new Rectangle(0, 0, Width, Height));
}
// The colored line in front of the slider
using (Pen pen = new Pen(TrackBorderColor2, TrackThickness)) {
e.Graphics.DrawLine(pen, Padding.Left, Height / 2, Width - Padding.Right, Height / 2);
}
// The colored line following the slider
using (Pen pen = new Pen(TrackBorderColor, TrackThickness)) {
e.Graphics.DrawLine(pen, Padding.Left, Height / 2, ThumbRect.Right, Height / 2);
}
// Filling the slider
using (SolidBrush brush2 = new SolidBrush(Color.FromArgb(100, 0, 0, 0))) {
e.Graphics.FillRectangle(brush2, ThumbRect);
}
}
#endregion
}
Thanks to the help of user #IVSoftware, in writing auxiliary methods calcValueFromPosition() and calcXfromValue(), the following solution was obtained, which allows you to set different paddings:
[Code Custom Trackbar]
namespace Handy_UI.Controls.Trackbars {
[DefaultEvent("ValueChanged")]
public class HandyHTrackbarWorked : Control {
#region Setting the initial parameters
public HandyHTrackbarWorked() {
SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.UserPaint
| ControlStyles.AllPaintingInWmPaint | ControlStyles.ResizeRedraw, true); UpdateStyles();
Size = new Size(250, 12);
ThumbSize = new Size(15, 12);
}
#endregion
#region Main features
private double _value;
public double Value {
get { return _value; }
set {
_value = value;
OnScroll(); Refresh();
}
}
private double minimum;
public double Minimum {
get { return minimum; }
set { minimum = value; Invalidate(); }
}
private double maximum = 100;
public double Maximum {
get { return maximum; }
set { maximum = value; Invalidate(); }
}
private double smallStep = 1;
public double SmallStep {
get { return smallStep; }
set {
smallStep = (value > 0) ? value : 1;
}
}
#endregion
#region Properties responsible for design
private Size thumbSize;
public Size ThumbSize {
get { return thumbSize; }
set {
thumbSize = value;
Invalidate();
}
}
private Color thumbBackColor = Color.FromArgb(255, 255, 255);
public Color ThumbBackColor {
get { return thumbBackColor; }
set { thumbBackColor = value; Invalidate(); }
}
private Color trackBackColor = Color.Transparent;
public Color TrackBackColor {
get { return trackBackColor; }
set { trackBackColor = value; Invalidate(); }
}
private Color trackBorderColor = Color.FromArgb(221, 0, 49);
public Color TrackBorderColor {
get { return trackBorderColor; }
set { trackBorderColor = value; Invalidate(); }
}
private Color trackBorderColor2 = Color.FromArgb(64, 64, 64);
public Color TrackBorderColor2 {
get { return trackBorderColor2; }
set { trackBorderColor2 = value; Invalidate(); }
}
private int trackThickness = 2;
public int TrackThickness {
get { return trackThickness; }
set { trackThickness = value; Invalidate(); }
}
public new Padding Padding {
get { return base.Padding; }
set { base.Padding = value; Invalidate(); }
}
#endregion
#region Key Events
public event EventHandler ValueChanged;
#endregion
#region Обработчики событий
private Point startMouseClickPosition;
private Point currentMousePosition;
protected override void OnCreateControl() {
base.OnCreateControl();
this.MouseDown += (sender, e) => {
// When clicking on the ScrollBar, center the Thumb relative to the mouse cursor
if (!ThumbRect.Contains(e.Location)) {
MoveThumb(e, false);
}
// When clicking on Thumb, determine the startMouseClickPosition
if (ThumbRect.Contains(e.Location)) {
startMouseClickPosition.X = e.X - ThumbRect.Left; // OR ... - ThumbRect.X
ThumbBackColor = Color.Green;
}
};
this.MouseMove += (sender, e) => {
ThumbBackColor = ThumbRect.Contains(e.Location)
? ThumbBackColor = Color.Orange : ThumbBackColor = Color.Gray;
if (e.Button == MouseButtons.Left) {
ThumbBackColor = Color.Green; MoveThumb(e);
}
};
this.MouseLeave += (sender, e) => { ThumbBackColor = Color.Gray; };
//this.SizeChanged += (sender, e) => { TrackThickness = Height - Padding.Bottom - Padding.Top; };
}
private void MoveThumb(MouseEventArgs e, bool useStartMouseClickPosition = true) {
Point currentMousePosition = new Point(0, 0);
if (useStartMouseClickPosition) {
currentMousePosition.X = e.X - startMouseClickPosition.X + ThumbSize.Width / 2
- Padding.Right - (Padding.Left - Padding.Right);
} else currentMousePosition.X = e.X - Padding.Left;
Value = calcValueFromPosition(currentMousePosition);
}
private double calcValueFromPosition(Point e) {
var mouseRange = Width - (Padding.Left + Padding.Right);
var pct = e.X / (double)mouseRange;
var controlRange = Maximum - Minimum;
var relative = pct * controlRange;
var value = Minimum + relative;
value = Math.Max(Minimum, value);
value = Math.Min(Maximum, value);
return value;
}
public void OnScroll() {
ValueChanged?.Invoke(this, EventArgs.Empty);
}
#endregion
#region Drawing controls
public int calcXfromValue() {
var range = Maximum - Minimum;
var relative = Value - Minimum;
var pct = relative / range;
var width = Width - (Padding.Left + Padding.Right);
var pos = pct * width;
var x = pos + Padding.Left - (ThumbSize.Width / 2);
if (x < 0 + Padding.Left) x = 0 + Padding.Left;
else if (x > Width - ThumbSize.Width - Padding.Right) x = Width - ThumbSize.Width - Padding.Right;
return (int)x;
}
public Rectangle ThumbRect => new Rectangle(
x: calcXfromValue(), y: 0 + Padding.Top,
width: ThumbSize.Width, // fixed slider width
height: Height - Padding.Bottom - Padding.Top // dynamic slider width
);
protected override void OnPaint(PaintEventArgs e) {
// Filling the scroll bar
using (SolidBrush brush = new SolidBrush(TrackBackColor)) {
e.Graphics.FillRectangle(brush, new Rectangle(0, 0, Width, Height));
}
// The colored line in front of the slider
using (Pen pen = new Pen(TrackBorderColor2, TrackThickness)) {
e.Graphics.DrawLine(pen, Padding.Left, Height / 2, Width - Padding.Right, Height / 2);
}
// The colored line following the slider
using (Pen pen = new Pen(TrackBorderColor, TrackThickness)) {
e.Graphics.DrawLine(pen, Padding.Left, Height / 2, ThumbRect.Right, Height / 2);
}
// Filling the slider
using (SolidBrush brush2 = new SolidBrush(Color.FromArgb(100, 0, 0, 0))) {
e.Graphics.FillRectangle(brush2, ThumbRect);
}
}
#endregion
}
}
I have a widget made up of 2 TextViews, 2 ImageViews, and 2 TextClocks. I am trying to update the widget using a ScreenListener (code below) and an alarm. I want all views to update when the screen is turned On. Also, a repeating alarm is set when the screen is turned On. The alarm is used to trigger the update of the battery level indicator (an ImageView). The image for the battery level indicator is generated using the ProgressRing (code below). The alarm is cancelled when the screen is turned Off. The TextClocks take care of themselves.
Everything works when debugging using the emulator or my phone. The various components update when I turn the screen On and Off. However, when I install the widget on my phone, the widget stops updating after a period of time. Initially, I can turn the screen On and Off and everything updates. And, I can plug (or unplug) the phone and the alarm will update the battery level indicator. But, after some period of time (I think with the screen Off), updating stops. Turning the screen On no longer updates anything and, I beleive, the alarm is no longer set. (Note: The TextClock continue to work and show the correct time.)
When debugging, I have seen the instances of the ScreenLister and ProgressRing become null. So, I've included checks for this and make new instances when it happens. This doesn't seem like the right solution. Should this be occurring?
Is the system killing my widget? Is there a way around this, if so? Is my AppWidgetProvider somehow losing it's connection to the RemoteViews?
Thanks for any help. And, let me know if you need more information.
P.S. I'm currently building for Android 9.0. And, I'm new to Android programming.
AppWidgetProvider
namespace ClockCalBattery
{
[BroadcastReceiver(Enabled = true)]
[IntentFilter(new string[] { "android.appwidget.action.APPWIDGET_UPDATE", Intent.ActionUserPresent})]
[MetaData("android.appwidget.provider", Resource = "#xml/appwidgetprovider")]
public class CCBWidget : AppWidgetProvider, ScreenStateListener
{
public static ProgressRing progressRing = new ProgressRing();
private static String ACTION_UPDATE_BATTERY = "com.lifetree.clockcalbattery.UPDATE-BATTERY";
public static ScreenListener mScreenListener;
public static int n_alarms = 0;
public override void OnReceive(Context context, Intent intent)
{
base.OnReceive(context, intent);
if (intent.Action == ACTION_UPDATE_BATTERY)
{
update_batteryLevel(context);
if (mScreenListener == null)
{
mScreenListener = new ScreenListener(context);
mScreenListener.begin(this);
}
if (progressRing == null)
{
progressRing = new ProgressRing();
}
}
}
public override void OnEnabled(Context context)
{
base.OnEnabled(context);
if (mScreenListener == null)
{
mScreenListener = new ScreenListener(context);
mScreenListener.begin(this);
}
if (progressRing == null)
{
progressRing = new ProgressRing();
}
update_batteryLevel(Application.Context);
update_nextAlarm(Application.Context);
}
public override void OnDisabled(Context context)
{
base.OnDisabled(context);
}
public override void OnUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds)
{
base.OnUpdate(context, appWidgetManager, appWidgetIds);
mScreenListener = new ScreenListener(context);
progressRing = new ProgressRing();
var me = new ComponentName(context, Java.Lang.Class.FromType(typeof(CCBWidget)).Name);
var widgetView = BuildRemoteViews(context, appWidgetIds);
appWidgetManager.UpdateAppWidget(me, widgetView);
}
private RemoteViews BuildRemoteViews(Context context, int[] appWidgetIds)
{
RemoteViews widgetView = new RemoteViews(context.PackageName, Resource.Layout.CCBWidget);
update_TodaysDate(widgetView);
update_nextAlarm(context, widgetView);
update_batteryLevel(widgetView);
return widgetView;
}
private void update_TodaysDate(RemoteViews widgetView)
{
string today = DateTime.Today.ToString("D");
widgetView.SetTextViewText(Resource.Id.longDate, today);
}
private void update_nextAlarm(Context context, RemoteViews widgetView)
{
AlarmManager am = (AlarmManager)context.GetSystemService(Context.AlarmService);
AlarmManager.AlarmClockInfo alarmInfo = am.NextAlarmClock;
if (alarmInfo != null)
{
long time = alarmInfo.TriggerTime;
DateTime alarmDateTime = new DateTime(1970, 1, 1).AddMilliseconds(time).ToLocalTime();
string alarmTime = alarmDateTime.ToString("ddd ");
alarmTime += alarmDateTime.ToString("t");
widgetView.SetTextViewText(Resource.Id.textView_alarmTime, alarmTime);
widgetView.SetImageViewResource(Resource.Id.imageView_alarmIcon, Resource.Drawable.whiteOnBlack_clock);
}
else
{
widgetView.SetTextViewText(Resource.Id.textView_alarmTime, "");
widgetView.SetImageViewResource(Resource.Id.imageView_alarmIcon, Resource.Drawable.navigation_empty_icon);
}
}
private void update_nextAlarm(Context mContext)
{
RemoteViews widgetView = new RemoteViews(mContext.PackageName, Resource.Layout.CCBWidget);
update_nextAlarm(mContext, widgetView);
var me = new ComponentName(mContext, Java.Lang.Class.FromType(typeof(CCBWidget)).Name);
var awm = AppWidgetManager.GetInstance(mContext);
awm.UpdateAppWidget(me, widgetView);
}
private void update_batteryLevel(RemoteViews widgetView)
{
progressRing.setProgress((int)(Battery.ChargeLevel * 100.0));
progressRing.drawProgressBitmap();
widgetView.SetImageViewBitmap(Resource.Id.progressRing, progressRing.ring);
}
private void update_batteryLevel(Context mContext)
{
RemoteViews widgetView = new RemoteViews(mContext.PackageName, Resource.Layout.CCBWidget);
update_batteryLevel(widgetView);
var me = new ComponentName(mContext, Java.Lang.Class.FromType(typeof(CCBWidget)).Name);
var awm = AppWidgetManager.GetInstance(mContext);
awm.UpdateAppWidget(me, widgetView);
}
public static void turnUpdateAlarmOnOff(Context context, bool turnOn, int time)
{
AlarmManager alarmManager = (AlarmManager)context.GetSystemService(Context.AlarmService);
Intent intent = new Intent(context, typeof(CCBWidget));
intent.SetAction(ACTION_UPDATE_BATTERY);
PendingIntent pendingIntent = PendingIntent.GetBroadcast(context, 0, intent, 0);
if (turnOn)
{
// Add extra 1 sec because sometimes ACTION_BATTERY_CHANGED is called after the first alarm
alarmManager.SetInexactRepeating(AlarmType.ElapsedRealtime, SystemClock.ElapsedRealtime() + 1000, time * 1000, pendingIntent);
n_alarms++;
}
else
{
alarmManager.Cancel(pendingIntent);
n_alarms--;
}
}
public void onScreenOn()
{
if (mScreenListener == null)
{
mScreenListener = new ScreenListener(Application.Context);
mScreenListener.begin(this);
}
if (progressRing == null)
{
progressRing = new ProgressRing();
}
update_batteryLevel(Application.Context);
update_nextAlarm(Application.Context);
turnUpdateAlarmOnOff(Application.Context, true, 60);
}
public void onScreenOff()
{
turnUpdateAlarmOnOff(Application.Context, false, 1);
}
public void onUserPresent()
{
//Console.WriteLine("onUserPresent");
}
}
}
ScreenListener
namespace ClockCalBattery
{
public class ScreenListener
{
private Context mContext;
private ScreenBroadcastReceiver mScreenReceiver;
private static ScreenStateListener mScreenStateListener;
public ScreenListener(Context context)
{
mContext = context;
mScreenReceiver = new ScreenBroadcastReceiver();
}
/**
* screen BroadcastReceiver
*/
private class ScreenBroadcastReceiver : BroadcastReceiver
{
private String action = null;
public override void OnReceive(Context context, Intent intent)
{
action = intent.Action;
if (Intent.ActionScreenOn == action)
{ // screen on
mScreenStateListener.onScreenOn();
}
else if (Intent.ActionScreenOff == action)
{ // screen off
mScreenStateListener.onScreenOff();
}
else if (Intent.ActionUserPresent == action)
{ // unlock
mScreenStateListener.onUserPresent();
}
}
}
/**
* begin to listen screen state
*
* #param listener
*/
public void begin(ScreenStateListener listener)
{
mScreenStateListener = listener;
registerListener();
getScreenState();
}
/**
* get screen state
*/
private void getScreenState()
{
PowerManager manager = (PowerManager)mContext
.GetSystemService(Context.PowerService);
if (manager.IsInteractive)
{
if (mScreenStateListener != null)
{
mScreenStateListener.onScreenOn();
}
}
else
{
if (mScreenStateListener != null)
{
mScreenStateListener.onScreenOff();
}
}
}
/**
* stop listen screen state
*/
public void unregisterListener()
{
mContext.UnregisterReceiver(mScreenReceiver);
}
/**
* regist screen state broadcast
*/
private void registerListener()
{
IntentFilter filter = new IntentFilter();
filter.AddAction(Intent.ActionScreenOn);
filter.AddAction(Intent.ActionScreenOff);
filter.AddAction(Intent.ActionUserPresent);
mContext.ApplicationContext.RegisterReceiver(mScreenReceiver, filter);
//mContext.RegisterReceiver(mScreenReceiver, filter);
}
public interface ScreenStateListener
{// Returns screen status information to the caller
void onScreenOn();
void onScreenOff();
void onUserPresent();
}
}
}
ProgressRing
namespace ClockCalBattery
{
public class ProgressRing
{
private int max = 100;
public int progress;
private Path path = new Path();
Color color = new Color(50, 50, 255, 255);
private Paint paint;
private Paint mPaintProgress;
private RectF mRectF;
private Paint batteryLevelTextPaint;
private Paint batteryStateTextPaint;
private String batteryLevelText = "0%";
private String batteryStateText = null;
private BatteryState bs;
private Rect textBounds = new Rect();
private int centerY;
private int centerX;
private float swipeAndgle = 0;
public Bitmap ring;
private int bitmapWidth = 70;
private int bitmapHeight = 70;
public ProgressRing()
{
progress = -1;
paint = new Paint();
paint.AntiAlias = true;
paint.StrokeWidth = 1;
paint.SetStyle(Paint.Style.Stroke);
paint.Color = color;
mPaintProgress = new Paint();
mPaintProgress.AntiAlias = true;
mPaintProgress.SetStyle(Paint.Style.Stroke);
mPaintProgress.StrokeWidth = 5;
mPaintProgress.Color = color;
batteryLevelTextPaint = new Paint();
batteryLevelTextPaint.AntiAlias = true;
batteryLevelTextPaint.SetStyle(Paint.Style.Fill);
batteryLevelTextPaint.Color = color;
batteryLevelTextPaint.StrokeWidth = 1;
batteryStateTextPaint = new Paint();
batteryStateTextPaint.AntiAlias = true;
batteryStateTextPaint.SetStyle(Paint.Style.Fill); ;
batteryStateTextPaint.Color = color;
batteryStateTextPaint.StrokeWidth = 1;
//batteryStateTextPaint.SetTypeface(Typeface.Create(Typeface.Default, TypefaceStyle.Bold));
Init_ring();
}
private void Init_ring()
{
int viewWidth = bitmapWidth;
int viewHeight = bitmapHeight;
float radius = (float)(bitmapHeight / 2.0);
path.Reset();
centerX = viewWidth / 2;
centerY = viewHeight / 2;
path.AddCircle(centerX, centerY, radius, Path.Direction.Cw);
float smallCirclRadius = radius - (float)(0.1 * radius);
path.AddCircle(centerX, centerY, smallCirclRadius, Path.Direction.Cw);
//mRectF = new RectF(0, 0, viewWidth, viewHeight);
mRectF = new RectF(centerX - smallCirclRadius, centerY - smallCirclRadius, centerX + smallCirclRadius, centerY + smallCirclRadius);
batteryLevelTextPaint.TextSize = radius * 0.5f;
batteryStateTextPaint.TextSize = radius * 0.30f;
}
internal void setProgress(int progress)
{
this.progress = progress;
int percentage = progress * 100 / max;
swipeAndgle = percentage * 360 / 100;
batteryLevelText = percentage + "%";
batteryStateText = null;
bs = Battery.State;
if (bs == BatteryState.Charging)
batteryStateText = "Charging";
else if (percentage > 99)
batteryStateText = "Full";
else if (percentage < 15)
batteryStateText = "Low";
}
internal void drawProgressBitmap()
{
ring = Bitmap.CreateBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.Argb8888);
Canvas c = new Canvas(ring);
byte r = 0;
byte g = 0;
byte b = 0;
byte a = 255;
hls2rgb(swipeAndgle * (120.0 / 360.0), 1, 128, ref r, ref g, ref b);
paint.Color = new Color(r, g, b, a);
mPaintProgress.Color = new Color(r, g, b, a);
batteryLevelTextPaint.Color = new Color(r, g, b, a);
batteryStateTextPaint.Color = new Color(r, g, b, a);
c.DrawArc(mRectF, 270, swipeAndgle, false, mPaintProgress);
drawTextCentred(c);
}
private void drawTextCentred(Canvas c)
{
batteryLevelTextPaint.GetTextBounds(batteryLevelText, 0, batteryLevelText.Length, textBounds);
c.DrawText(batteryLevelText, centerX - textBounds.ExactCenterX(), centerY - textBounds.ExactCenterY(), batteryLevelTextPaint);
if (batteryStateText != null)
{
batteryStateTextPaint.GetTextBounds(batteryStateText, 0, batteryStateText.Length, textBounds);
c.DrawText(batteryStateText, centerX - textBounds.ExactCenterX(), centerY - textBounds.ExactCenterY() + 15, batteryStateTextPaint);
}
}
public void hls2rgb(double color_wheel_angle, double tlen, byte ilum,
ref byte color_r, ref byte color_g, ref byte color_b)
{
double rlum, rm1, rm2;
if (ilum > 255) ilum = 255;
if (ilum < 0) ilum = 0;
if (tlen > 1) tlen = 1;
if (tlen < 0) tlen = 0;
rlum = (double)ilum / 255;
if (rlum < 0.5)
rm2 = rlum * (1.0 + tlen);
else
rm2 = rlum + tlen - (rlum * tlen);
rm1 = (2.0 * rlum) - rm2;
if (tlen == 0)
{
color_r = ilum;
color_g = ilum;
color_b = ilum;
}
else
{
color_g = (byte)(value(rm1, rm2, color_wheel_angle + 120) * 255);
color_b = (byte)(value(rm1, rm2, color_wheel_angle) * 255);
color_r = (byte)(value(rm1, rm2, color_wheel_angle - 120) * 255);
}
return;
}
public double value(double r1, double r2, double angle)
{
double value;
angle = angle - 120;
if (angle > 360) angle = angle - 360;
if (angle < 0) angle = angle + 360;
if (angle < 60)
value = r1 + (r2 - r1) * angle / 60;
else if (angle < 180)
value = r2;
else if (angle < 240)
value = r1 + (r2 - r1) * (240 - angle) / 60;
else
value = r1;
return (value);
}
}
}
I'm giving my panels inside another panel (this panel is in a usercontrol) a fixed location and a maximum size that changes with the size of the panel there in. Neither the resize or location works properly. The resize does happen but its to quickly. The location is fine if you only have 1 pinpanel for output and input. When you have more then 1 the locations are fixed but you need to resize the panel to resize to see the other panels. Could you point me in the right direction if you see the problem?
I have a panel drawPanel in this case that i use as a sort of background for the usercontrol. Inside this drawPanel i'm placing pinpanels. I want these pinpanels to resize with the usercontrol and give them a fixed location
private void OnClickPinPanel(object source, EventArgs e)
{
if (source is PinPanel p)
{
int index;
if ((index = Array.IndexOf(inputPins, p)) >= 0)
{
ClickedPinPanel?.Invoke(index, true);
}
else
{
ClickedPinPanel?.Invoke(Array.IndexOf(outputPins, p), false);
}
}
//else log here
}
private void CreatePinPanels(bool isInput)
{
int x = 0;
int y = -(int)(this.Width * 0.05)/2;
if (isInput)
{
for (int i = 0; i < inputPins.Length; i++)
{
y += (i + 1) * (this.Height / inputPins.Length + 1);
inputPins[i] = new PinPanel()
{
Location = new Point(x, y),
Size = new Size((int)(this.Width * 0.05), (int)(this.Width * 0.05)),
Anchor = AnchorStyles.Left | AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Right,
};
inputPins[i].Click += OnClickPinPanel;
}
}
else
{
x += this.Width - (int)(this.Width * 0.1);
for (int i = 0; i < outputPins.Length; i++)
{
y += (i + 1) * (this.Height / inputPins.Length+1);
outputPins[i] = new PinPanel()
{
Size = new Size((int)(this.Width * 0.1), (int)(this.Width * 0.1)),
Location = new Point(x, y),
Anchor = AnchorStyles.Left | AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Right
};
outputPins[i].Click += OnClickPinPanel;
}
}
}
The result i get now is that the pinpanels get a fixed location but when you have more then 1 pinpanel, the location is wrong its like if he thinks that the usercontrol is bigger then it is Reality. In order to see all the pins i have to resize and get this After resize
I want it to look like this
expectations
Try something like this out.
Here is my test rig:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
numericUpDown1.Value = someChip1.NumberInputPins;
numericUpDown2.Value = someChip1.NumberOutputPins;
}
private void numericUpDown1_ValueChanged(object sender, EventArgs e)
{
someChip1.NumberInputPins = (int)numericUpDown1.Value;
}
private void numericUpDown2_ValueChanged(object sender, EventArgs e)
{
someChip1.NumberOutputPins = (int)numericUpDown2.Value;
}
}
Sample chip with 5 inputs and 3 outputs:
Here is my PinPanel UserControl (just draws an ellipse/pin the size of the control):
public partial class PinPanel : UserControl
{
public PinPanel()
{
InitializeComponent();
}
private void PinPanel_Paint(object sender, PaintEventArgs e)
{
Rectangle rc = new Rectangle(new Point(0, 0), new Size(this.ClientRectangle.Width - 1, this.ClientRectangle.Height - 1));
e.Graphics.DrawEllipse(Pens.Black, rc);
}
private void PinPanel_SizeChanged(object sender, EventArgs e)
{
this.Refresh();
}
}
And finally, my SomeChip UserControl:
public partial class SomeChip : UserControl
{
public event PinPanelClick ClickedPinPanel;
public delegate void PinPanelClick(int index, bool input);
private PinPanel[] inputPins;
private PinPanel[] outputPins;
private int _NumberInputPins = 2;
public int NumberInputPins
{
get {
return _NumberInputPins;
}
set
{
if (value > 0 && value != _NumberInputPins)
{
_NumberInputPins = value;
CreatePinPanels();
this.Refresh();
}
}
}
private int _NumberOutputPins = 1;
public int NumberOutputPins
{
get
{
return _NumberOutputPins;
}
set
{
if (value > 0 && value != _NumberOutputPins)
{
_NumberOutputPins = value;
CreatePinPanels();
this.Refresh();
}
}
}
public SomeChip()
{
InitializeComponent();
}
private void SomeChip_Load(object sender, EventArgs e)
{
CreatePinPanels();
RepositionPins();
}
private void SomeChip_SizeChanged(object sender, EventArgs e)
{
this.Refresh();
}
private void SomeChip_Paint(object sender, PaintEventArgs e)
{
int PinHeight;
// draw the input pin runs
if (inputPins != null)
{
PinHeight = (int)((double)this.Height / (double)_NumberInputPins);
for (int i = 0; i < NumberInputPins; i++)
{
int Y = (i * PinHeight) + (PinHeight / 2);
e.Graphics.DrawLine(Pens.Black, 0, Y, this.Width / 4, Y);
}
}
// draw the output pin runs
if (outputPins != null)
{
PinHeight = (int)((double)this.Height / (double)_NumberOutputPins);
for (int i = 0; i < NumberOutputPins; i++)
{
int Y = (i * PinHeight) + (PinHeight / 2);
e.Graphics.DrawLine(Pens.Black, this.Width - this.Width / 4, Y, this.Width, Y);
}
}
//draw the chip itself (takes up 50% of the width of the UserControl)
Rectangle rc = new Rectangle(new Point(this.Width / 4, 0), new Size(this.Width / 2, this.Height - 1));
using (SolidBrush sb = new SolidBrush(this.BackColor))
{
e.Graphics.FillRectangle(sb, rc);
}
e.Graphics.DrawRectangle(Pens.Black, rc);
RepositionPins();
}
private void CreatePinPanels()
{
if (inputPins != null)
{
for (int i = 0; i < inputPins.Length; i++)
{
if (inputPins[i] != null && !inputPins[i].IsDisposed)
{
inputPins[i].Dispose();
}
}
}
inputPins = new PinPanel[_NumberInputPins];
for (int i = 0; i < inputPins.Length; i++)
{
inputPins[i] = new PinPanel();
inputPins[i].Click += OnClickPinPanel;
this.Controls.Add(inputPins[i]);
}
if (outputPins != null)
{
for (int i = 0; i < outputPins.Length; i++)
{
if (outputPins[i] != null && !outputPins[i].IsDisposed)
{
outputPins[i].Dispose();
}
}
}
outputPins = new PinPanel[_NumberOutputPins];
for (int i = 0; i < outputPins.Length; i++)
{
outputPins[i] = new PinPanel();
outputPins[i].Click += OnClickPinPanel;
this.Controls.Add(outputPins[i]);
}
}
private void OnClickPinPanel(object sender, EventArgs e)
{
PinPanel p = (PinPanel)sender;
if (inputPins.Contains(p))
{
ClickedPinPanel?.Invoke(Array.IndexOf(inputPins, p), true);
}
else if (outputPins.Contains(p))
{
ClickedPinPanel?.Invoke(Array.IndexOf(inputPins, p), false);
}
}
private void RepositionPins()
{
int PinRowHeight, PinHeight;
if (inputPins != null)
{
PinRowHeight = (int)((double)this.Height / (double)_NumberInputPins);
PinHeight = (int)Math.Min((double)(PinRowHeight / 2), (double)this.Height * 0.05);
for (int i = 0; i < inputPins.Length; i++)
{
if (inputPins[i] != null && !inputPins[i].IsDisposed)
{
inputPins[i].SetBounds(0, (int)((i * PinRowHeight) + (PinRowHeight /2 ) - (PinHeight / 2)), PinHeight, PinHeight);
}
}
}
if (outputPins != null)
{
PinRowHeight = (int)((double)this.Height / (double)_NumberOutputPins);
PinHeight = (int)Math.Min((double)(PinRowHeight / 2), (double)this.Height * 0.05);
for (int i = 0; i < outputPins.Length; i++)
{
if (outputPins[i] != null && !outputPins[i].IsDisposed)
{
outputPins[i].SetBounds(this.Width - PinHeight, (int)((i * PinRowHeight) + (PinRowHeight / 2) - (PinHeight / 2)), PinHeight, PinHeight);
}
}
}
}
}
This is a custom control I have made, a graphical timer, however, it is not working correctly. As the time left decreases a pie is filled to represent the amount of time left decreasing, but it is decreasing with unexpected angles (which are output to the listbox for debugging purposes). How do I get it to work correctly? Also I'm very new to making Custom Controls (this is my first) so any pointers on good coding guidelines, what not to do, etc, would be very helpful.
using System;
using System.Drawing;
using System.Windows.Forms;
namespace TestCustomControl
{
class GraphicalTimer : Control
{
public Color Timer { get; set; }
public Color TimerEmpty { get; set; }
public Color BorderColor { get; set; }
private Timer t;
public int MaxTime { get; set; }
private int timeElapsed = 0;
public GraphicalTimer()
{
DoubleBuffered = true;
SetStyle(ControlStyles.SupportsTransparentBackColor, true);
BackColor = Color.Transparent;
t = new Timer();
t.Interval = 1000;
t.Tick += t_Tick;
}
public void Start()
{
t.Start();
}
public void Stop()
{
t.Stop();
}
public void Reset()
{
timeElapsed = 0;
Invalidate();
}
void t_Tick(object sender, EventArgs e)
{
timeElapsed += 1;
if (timeElapsed == MaxTime)
{
t.Dispose();
}
Invalidate();
}
private float getAngleFromTime()
{
if (timeElapsed == 0)
{
return 0;
}
else
{
MainWindow.lb.Items.Add((360 / (MaxTime / timeElapsed)).ToString());
return (360 / (MaxTime / timeElapsed));
}
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
Graphics g = e.Graphics;
Rectangle rc = ClientRectangle;
g.FillEllipse(new SolidBrush(Timer), rc);
g.FillPie(new SolidBrush(TimerEmpty), rc, -90, getAngleFromTime());
g.DrawEllipse(new Pen(BorderColor, 4), rc);
Font font = new Font("Arial", (float)rc.Height * 0.4f, FontStyle.Bold, GraphicsUnit.Pixel);
StringFormat sf = new StringFormat();
sf.Alignment = StringAlignment.Center;
sf.LineAlignment = StringAlignment.Center;
g.DrawString((MaxTime - timeElapsed).ToString("D2"), font, new SolidBrush(Color.Black), new RectangleF((rc.Width - rc.Width / 2) / 2, (rc.Height - rc.Height / 2) / 2, rc.Width * 0.7f, rc.Height * 0.7f));
}
}
class MainWindow : Form
{
GraphicalTimer gt;
Button startButton;
Button stopButton;
Button resetButton;
public static ListBox lb;
public MainWindow()
{
this.Text = "Test Application";
gt = new GraphicalTimer();
gt.MaxTime = 20;
gt.BorderColor = Color.BurlyWood;
gt.Timer = Color.Aqua;
gt.TimerEmpty = Color.White;
gt.Top = 10;
gt.Left = 10;
gt.Width = 50;
gt.Height = 50;
this.Controls.Add(gt);
startButton = new Button();
startButton.Top = 70;
startButton.Left = 30;
startButton.AutoSize = true;
startButton.Text = "Start Timer";
startButton.Click += startButton_Click;
this.Controls.Add(startButton);
stopButton = new Button();
stopButton.Top = 70;
stopButton.Left = startButton.Right + 10;
stopButton.AutoSize = true;
stopButton.Text = "Stop Timer";
stopButton.Click += stopButton_Click;
this.Controls.Add(stopButton);
resetButton = new Button();
resetButton.Top = 70;
resetButton.Left = stopButton.Right + 10;
resetButton.AutoSize = true;
resetButton.Text = "Reset Timer";
resetButton.Click += resetButton_Click;
this.Controls.Add(resetButton);
lb = new ListBox();
lb.Top = resetButton.Bottom + 10;
lb.Left = 10;
lb.Width = this.ClientSize.Width - 20;
lb.Height = this.ClientSize.Height - lb.Top - 10;
this.Controls.Add(lb);
}
void resetButton_Click(object sender, EventArgs e)
{
gt.Reset();
}
void stopButton_Click(object sender, EventArgs e)
{
gt.Stop();
}
void startButton_Click(object sender, EventArgs e)
{
gt.Start();
}
}
class StartClass
{
static void Main()
{
MainWindow form = new MainWindow();
Application.EnableVisualStyles();
Application.Run(form);
}
}
}
You are using integers to calculate the angle, and in a way that can cause quite some jitters.
360 / (MaxTime / timeElapsed) will first evaluate temp = MaxTime / timeElapsed in integer arithmetics, and then 360 / temp also using integer division.
Try using floating point numbers for the calculation and then converting the final result into an integer value if you need it that way. Even writing 360 * timeElapsed / MaxTime might reduce the artifacts as you then first multiply 360 * timeElapsed which is accurate (unless timeElapsed is very large).
The method of calculating AngleForTime is wrong. Just replace your method with below code and it should do your job.
;)
private float getAngleFromTime()
{
if (timeElapsed == 0)
{
return 0;
}
else
{
MainWindow.lb.Items.Add((360*timeElapsed) / MaxTime ).ToString();
return (360*timeElapsed) / MaxTime ;
}
}
I´m currently trying to add parallel downloads to my application but I don´t know how to handle the DownloadProgressChangedEvent to display the progress in multiple progressbars.
I´m using a datagridview with predefined rows for each file the user is able to download and each row has a cell with a progressbar in it.
The problem now is, that I don´t know how to update each progressbar individually, because right now, all selected progressbars are showing the same percentage and they´re just jumping between the progress of download1 & download2.
Here´s the code im using:
To start the downloads:
private void download_button_Click(object sender, EventArgs e)
{
start = DateTime.Now;
download_button.Enabled = false;
Rows = dataGridView1.Rows.Count;
Checked = 0;
CheckedCount = 0;
//count the selected rows
for (i = 0; i < Rows; i++)
{
Checked = Convert.ToInt32(dataGridView1.Rows[i].Cells["checkboxcol"].FormattedValue);
CheckedCount += Checked;
richTextBox3.Text = CheckedCount.ToString();
}
for (int z = 1; z < CheckedCount; z++)
{
_MultipleWebClients = new WebClient();
_MultipleWebClients.DownloadFileCompleted += new AsyncCompletedEventHandler(_DownloadFileCompleted);
_MultipleWebClients.DownloadProgressChanged += new System.Net.DownloadProgressChangedEventHandler(_DownloadProgressChanged);
_MultipleWebClients.DownloadFileAsync(new Uri(_downloadUrlList[z].ToString()), #"F:\test" + z + ".mp4");
}
}
(I´m also unable to download more than two files simultaneously - the third download won´t start until the first two are finished)
DownloadProgressChangedEvent:
private void _DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
{
progressBar1.Value = e.ProgressPercentage;
for (int c = 0; c < CheckedCount; c++)
{
dataGridView1.Rows[_downloadRowNrList[c]].Cells[3].Value = e.ProgressPercentage;
}
float size = ((e.TotalBytesToReceive / 1024) / 1024);
label1.Text = size.ToString();
double dn = (double)e.BytesReceived / 1024.0 / (DateTime.Now - start).TotalSeconds;
label2.Text = (dn.ToString("n") + " KB/s) " + e.ProgressPercentage);
}
The problem probably is, that all progressbars are using the same DownloadProgressChangedEvent, but I´m not sure how to create multiple of these events without knowing the needed number...
So i hope that someone is able to help me with this,
thanks in advance!
What you want to do is use the other DownloadFileAsync method:
http://msdn.microsoft.com/en-us/library/ms144197.aspx
The third parameter is a userToken which gets passed as part of the DownloadProgressChangedEventArgs (it's in the UserState property).
So, when you make the DownloadFileAsync call, pass in a unique token (an integer, or something else) that you can then associate with the progressBar that needs updating.
//(Snip)
//in download_button_Click, pass the row you are updating to the event.
for (int z = 1; z < CheckedCount; z++)
{
_MultipleWebClients = new WebClient();
_MultipleWebClients.DownloadFileCompleted += new AsyncCompletedEventHandler(_DownloadFileCompleted);
_MultipleWebClients.DownloadProgressChanged += new System.Net.DownloadProgressChangedEventHandler(_DownloadProgressChanged);
_MultipleWebClients.DownloadFileAsync(new Uri(_downloadUrlList[z].ToString()), #"F:\test" + z + ".mp4", dataGridView1.Rows[z]);
}
}
private void _DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
{
var rowToUpdate = (DataGridViewRow)e.UserState;
RowToUpdate["ProgressBar"].Value = e.ProgressPercentage;
RowToUpdate["TextProgress"].Value = e.ProgressPercentage;
RowToUpdate["BytesToRecive"].Value = ((e.TotalBytesToReceive / 1024) / 1024).ToString();
double dn = (double)e.BytesReceived / 1024.0 / (DateTime.Now - start).TotalSeconds;
RowToUpdate["Speed"].Value = (dn.ToString("n") + " KB/s) " + e.ProgressPercentage);
}
Sounds like you need a progress bar for multi-parted progress:
public partial class ProgressBarEx : ProgressBar
{
private readonly Dictionary<Guid, double> _partsProgress =
new Dictionary<Guid, double>();
private readonly Dictionary<Guid, double> _partsSizes =
new Dictionary<Guid, double>();
private double _value;
private double _maximum;
public ProgressBarEx()
{
this.InitializeComponent();
}
public int Parts
{
get { return this._partsSizes.Count; }
}
public new int Minimum { get; private set; }
public new double Maximum
{
get { return this._maximum; }
private set
{
this._maximum = value;
base.Maximum = (int)value;
}
}
public new double Value
{
get { return this._value; }
private set
{
this._value = value;
base.Value = (int)value;
}
}
[Obsolete("Not useable in ProgressBarEx.")]
public new int Step
{
get { return 0; }
}
public Guid AddPart(double size)
{
if (size <= 0)
{
throw new ArgumentException("size");
}
var partId = Guid.NewGuid();
this.Maximum += size;
this._partsSizes.Add(partId, size);
this._partsProgress.Add(partId, 0);
return partId;
}
public bool RemovePart(Guid partId)
{
double size;
if (!this._partsSizes.TryGetValue(partId, out size))
{
return false;
}
this.Maximum -= size;
this._partsSizes.Remove(partId);
this.Value -= this._partsProgress[partId];
this._partsProgress.Remove(partId);
return true;
}
public bool ContainsPart(Guid partId)
{
return this._partsSizes.ContainsKey(partId);
}
public double GetProgress(Guid partId)
{
return this._partsProgress[partId];
}
public void SetProgress(Guid partId, double progress)
{
if (progress < 0 || this._partsSizes[partId] < progress)
{
throw new ArgumentOutOfRangeException("progress");
}
this.Value += progress - this._partsProgress[partId];
this._partsProgress[partId] = progress;
}
public void AddProgress(Guid partId, double progress)
{
this.SetProgress(partId, progress + this._partsProgress[partId]);
}
[Obsolete("Not useable in ProgressBarEx.")]
public new void PerformStep()
{
}
}
Example usage:
public Form1()
{
InitializeComponent();
var pbe = new ProgressBarEx {Location = new Point(100, 100)};
this.Controls.Add(pbe);
for (var i = 0; i < 4; i++)
{
var size = i * 10 + 30;
var partId = pbe.AddPart(size);
var pb = new ProgressBar
{
Maximum = size,
Location = new Point(100, i * 30 + 130)
};
this.Controls.Add(pb);
var timer = new Timer {Interval = 1000 + i * 100};
timer.Tick += (sender, args) =>
{
pb.Value += 5;
pbe.AddProgress(partId, 5);
if (pb.Value == pb.Maximum)
{
timer.Stop();
}
};
timer.Start();
}
}