Как создать слайдер с нелинейным масштабом?
У меня есть ползунок с минимальным значением 0 и максимум 500.
Я хочу, когда ползунок переходит на 100, большой палец будет посредине ползунка.
Я знаю, что это кажется странным, но некоторые программы делают это с помощью слайдера масштабирования, и я считаю, что это лучше.
Ответы
Ответ 1
Это был такой интересный вопрос, что я не мог оставить его в покое, и, надеюсь, я получил то, о чем вы спрашиваете:)
Вы хотите изменить Value
от Slider
от линейной до квадратичной функции, указав значение Y функции, когда Thumb находится посередине.
Квадратичная функция записывается на форме
![formula]()
Так как мы имеем 3 точки, мы имеем 3 набора значений для X и Y.
(X1, Y1) = 0, 0
(X2, Y2) = MiddleX, CenterQuadraticValue (in your case 100)
(X3, Y3) = Maximum, Maximum (in your case 500)
Отсюда мы можем создать квадратичное уравнение (см. эту ссылку, например), которая выходит на
![formula]()
К сожалению, некоторые значения в этом графе заканчиваются ниже 0, поэтому их нужно будет принудить к 0 (я включил график в нижней части ответа).
Я создал элемент управления QuadraticSlider
, который происходит от Slider
и добавляет два свойства зависимостей: QuadraticValue
и CenterQuadraticValue
. Он вычисляет QuadraticValue
с использованием приведенной выше формулы на основе Value
, Maximum
, Minimum
и CenterQuadraticValue
. Он также делает обратное: установка QuadraticValue
обновлений Value
. Поэтому вместо привязки к Value
привязывается к QuadraticValue
.
Изменить: Последняя версия была немного ошибкой. Исправлено несколько вещей
- Вычисление
Value
из QuadraticValue
больше не прерывается, когда "a" равно 0
- Использовал неправильный корень из решения второй степени, когда деривация была отрицательной.
Я загрузил пример приложения, где QuadraticSlider
используется для увеличения изображения. Все параметры могут быть указаны, и первое изображение использует Value
, а другое QuadraticValue
.
Загрузите его здесь, если вы хотите попробовать.
Похоже, что это
![enter image description here]()
И вот как выглядит график, обратите внимание на значения ниже 0
![enter image description here]()
Ответ 2
Хорошей формулой для отображаемого значения является монотонная функция, такая как кривая мощности, в следующем виде:
DisplayValue = A + B * Math.Exp(C * SliderValue);
Внутреннее значение ползунка (например, от 0 до 1) получается путем инвертирования формулы:
SliderValue = Math.Log((DisplayValue - A) / B) / C;
Теперь, как получить A, B и C? Используя три ограничения, которые вы указали:
f(0.0) = 0
f(0.5) = 100
f(1.0) = 500
Три уравнения, три неизвестных, это решаются с использованием основных математических данных:
A + B = 0
A + B exp(C * 0.5) = 100
A + B exp(C) = 500
B (exp(C * 0.5) - 1) = 100
B (exp(C) - 1) = 500
exp(C) - 5 exp(C * 0.5) + 4 = 0 // this is a quadratic equation
exp(C * 0.5) = 4
C = log(16)
B = 100/3
A = -100/3
Используя следующий код:
double B = 100.0 / 3;
double C = Math.Log(16.0);
DisplayValue = B * (Math.Exp(C * SliderValue) - 1.0);
Вы можете видеть, что отображаемое значение равно 100, когда внутреннее значение находится посередине:
![final curve]()
Изменить: поскольку запрошена общая формула, вот оно. Дано:
f(0.0) = x
f(0.5) = y
f(1.0) = z
Значения для A, B и C:
A = (xz - y²) / (x - 2y + z)
B = (y - x)² / (x - 2y + z)
C = 2 * log((z-y) / (y-x))
Обратите внимание, что если x - 2y + z
равно нулю, нет решения, и вы получите деление на ноль. То потому что в этом случае шкала фактически линейна. Вам необходимо позаботиться об этом специальном случае.
Ответ 3
пусть ползунок как есть, и используйте ValueConverter для ваших привязок. В ValueConverter используйте нелинейное масштабирование для масштабирования значения по вашему желанию.
Ответ 4
Как дополнительная ссылка; если вам не нужны точные позиции для вашего ползунка, соответствующие конкретным значениям в вашем масштабе, но все же хотите, чтобы поведение, при котором ползунок более чувствителен к значениям в начале шкалы, чем на конце, возможно, используя простой масштаб журнала может быть достаточно.
public class LogScaleConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
double x = (int)value;
return Math.Log(x);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
double x = (double)value;
return (int)Math.Exp(x);
}
}
Ответ 5
Некоторое дополнение к сообщению Meleak.
Я немного исправил QuadraticSlider. Возникла проблема с обработчиками событий (событие на QuadraticValueChanged со значением prevoius; событие во время инициализации с пределами диапазона [min, max]).
protected override void OnValueChanged(double oldValue, double newValue)
{
QuadraticValue = a * Math.Pow(Value, 2) + b * Value + c;
base.OnValueChanged(oldValue, newValue);
}
public double QuadraticValue
{
get {
var qv = (double)GetValue(QuadraticValueProperty);
if (double.IsNaN(qv))
qv = 0;
qv = Math.Max(qv, base.Minimum);
qv = Math.Min(qv, base.Maximum);
return qv;
}
set
{
SetValue(QuadraticValueProperty, value);
}
}
Ответ 6
На основе алгоритма из sam hocevar, вот код, который я собрал:
/// <summary>
/// Scale a linear range between 0.0-1.0 to an exponential scale using the equation returnValue = A + B * Math.Exp(C * inputValue);
/// </summary>
/// <param name="inoutValue">The value to scale</param>
/// <param name="midValue">The value returned for input value of 0.5</param>
/// <param name="maxValue">The value to be returned for input value of 1.0</param>
/// <returns></returns>
private double ExpScale(double inputValue, double midValue, double maxValue)
{
double returnValue = 0;
if (inputValue < 0 || inputValue > 1) throw new ArgumentOutOfRangeException("Input value must be between 0 and 1.0");
if (midValue <= 0 || midValue >= maxValue) throw new ArgumentOutOfRangeException("MidValue must be greater than 0 and less than MaxValue");
// returnValue = A + B * Math.Exp(C * inputValue);
double M = maxValue / midValue;
double C = Math.Log(Math.Pow(M - 1, 2));
double B = maxValue / (Math.Exp(C) - 1);
double A = -1 * B;
returnValue = A + B * Math.Exp(C * inputValue);
return returnValue;
}
Ответ 7
Обобщить Сэма Хочевара отличный ответ:
Пусть заданное максимальное значение равно M.
Пусть значение в средней точке слайдера равно m.
(очевидно, 0 < m < M), то
A = - M*m^2 / (M^2 - 2*m*M)
B = M*m^2 / (M^2 - 2*m*M)
C = Ln((M - m)^2 / m^2) // <- logarithm to the base of e, I always think of 'Log' as base 10
Нужно позаботиться о том, чтобы обрабатывать случай 2 * m = M отдельно, потому что это приводит к делению на 0. Но в любом случае вы должны иметь слайдер в линейном режиме.
Выбор m из M/2 и M приводит к логарифмической кривой: эффективные значения ползунка сначала повышаются быстро, а затем медленно. Это в основном отменяет эффект и дает пользователю более точное управление более высокими значениями.
Как уже упоминалось, m близко к M/2 делает слайдер в основном линейным.
Выбор m, близкого к 0 или близкого к M, позволяет точно контролировать очень низкие или очень высокие значения.
Я предполагаю, что можно использовать это в сочетании со вторым слайдером, который устанавливает m в значение от 0 до M, чтобы изменить... errr... чувствительную зону реального ползунка.