Comment créer un curseur avec une échelle non linéaire?

J’ai un curseur avec une valeur minimale de 0 et maximale de 500.

Je veux que lorsque le curseur passe à 100, le pouce soit au milieu du curseur.

Je sais que cela semble étrange, mais certains programmes le font avec le curseur de zoom, et je crois que c’est mieux.

C’était une question tellement intéressante que je ne pouvais pas la laisser seule et j’espère que j’ai bien compris ce que vous demandez 🙂

Vous souhaitez modifier la Value d’un Slider de linéaire à une fonction quadratique en spécifiant la valeur Y de la fonction lorsque le curseur est au milieu.

Une fonction quadratique est écrite sur le formulaire
formule

Puisque nous avons 3 points, nous avons 3 ensembles de valeurs pour X et Y.

 (X1, Y1) = 0, 0 (X2, Y2) = MiddleX, CenterQuadraticValue (in your case 100) (X3, Y3) = Maximum, Maximum (in your case 500) 

À partir de là, nous pouvons créer une équation quadratique ( voir ce lien par exemple ) qui revient à
formule

Malheureusement, certaines valeurs de ce graphique aboutissent en dessous de 0 et devront donc être forcées à 0 (j’ai inclus un graphique au bas de la réponse).

J’ai créé un contrôle, QuadraticSlider , qui dérive de Slider et ajoute deux propriétés de dépendance: QuadraticValue et CenterQuadraticValue . Il calcule QuadraticValue utilisant la formule ci-dessus basée sur Value , Maximum , Minimum et CenterQuadraticValue . Il effectue également l’inverse: définir QuadraticValue met à jour la Value . Ainsi, au lieu de Binding to Value , liez-vous à QuadraticValue .

Edit: La dernière version était un petit buggy. Correction de deux choses

  • Le calcul de la Value partir de QuadraticValue ne s’interrompt plus lorsque “a” vaut 0
  • Mauvaise racine utilisée dans la solution du deuxième degré lorsque le dérivé était négatif

J’ai téléchargé un exemple d’application dans lequel QuadraticSlider est utilisé pour zoomer une image. Tous les parameters peuvent être spécifiés et la première image utilise Value et l’autre QuadraticValue .

Téléchargez-le ici si vous voulez l’essayer.

Ça ressemble à ça
entrez la description de l'image ici

Et voici à quoi ressemble le graphique, remarquez les valeurs inférieures à 0

entrez la description de l'image ici

Une bonne formule pour la valeur affichée est une fonction monotone telle qu’une courbe de puissance, sous la forme suivante:

 DisplayValue = A + B * Math.Exp(C * SliderValue); 

La valeur du curseur interne (de 0 à 1 par exemple) est obtenue en inversant la formule:

 SliderValue = Math.Log((DisplayValue - A) / B) / C; 

Maintenant, comment obtenir A, B et C? En utilisant les trois contraintes que vous avez données:

 f(0.0) = 0 f(0.5) = 100 f(1.0) = 500 

Trois équations, trois inconnues, ceci est résolu en utilisant les mathématiques de base:

 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 

Cédant le code suivant:

 double B = 100.0 / 3; double C = Math.Log(16.0); DisplayValue = B * (Math.Exp(C * SliderValue) - 1.0); 

Vous pouvez voir que la valeur d’affichage est à 100 quand la valeur interne est au milieu:

courbe finale

Edit : depuis qu’une formule générique a été demandée, la voici. Donné:

 f(0.0) = x f(0.5) = y f(1.0) = z 

Les valeurs pour A, B et C sont:

 A = (xz - y²) / (x - 2y + z) B = (y - x)² / (x - 2y + z) C = 2 * log((zy) / (yx)) 

Notez que si x - 2y + z est zéro, il n’y a pas de solution et vous obtiendrez une division par zéro. C’est parce que dans ce cas, l’échelle est en réalité linéaire. Vous devez vous occuper de ce cas particulier.

laissez le curseur tel quel et utilisez un ValueConverter pour vos liaisons. Dans ValueConverter, utilisez la mise à l’échelle non linéaire pour mettre la valeur à l’échelle comme vous le souhaitez.

Juste comme une référence supplémentaire; si vous n’êtes pas intéressé par les positions exactes de votre curseur pour correspondre à des valeurs spécifiques de votre échelle mais souhaitez tout de même un comportement dans lequel le curseur est plus sensible aux valeurs au début de l’échelle qu’à la fin, utilisez peut-être une simple échelle de journal peut suffire.

 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); } } 

Basé sur l’algorithme de sam hocevar, voici le code que j’ai mis en place:

 ///  /// Scale a linear range between 0.0-1.0 to an exponential scale using the equation returnValue = A + B * Math.Exp(C * inputValue); ///  /// The value to scale /// The value returned for input value of 0.5 /// The value to be returned for input value of 1.0 ///  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; } 

Quelques ajouts au post de Meleak. J’ai légèrement corrigé QuadraticSlider. Il y avait un problème avec les gestionnaires d’événements (événement sur QuadraticValueChanged avec une valeur encore antérieure; événement lors de l’initialisation avec une valeur hors limites [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); } } 

Pour généraliser sur l’excellente réponse de Sam Hocevar:

Laissez la valeur maximale prévue être M.
Laissez la valeur au point médian du curseur être m.
(évidemment, 0

  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 

Il faut prendre soin de traiter le cas 2 * m = M séparément, car cela conduit à une division par 0. Mais dans ce cas, le curseur se comporterait de manière linéaire.

Si vous choisissez m entre M / 2 et M, vous obtenez une courbe logarithmique: les valeurs effectives du curseur augmentent rapidement au début, puis lentement. Cela inverse l’effet et donne à l’utilisateur un contrôle plus précis des valeurs les plus élevées.

Comme mentionné, un m proche de M / 2 rend le curseur fondamentalement linéaire.
Choisir m proche de 0 ou proche de M permet un contrôle précis des valeurs très basses ou très élevées.

Je suppose que l’on pourrait l’utiliser en combinaison avec un deuxième curseur qui définit m sur une valeur comprise entre 0 et M pour modifier la ... zone sensible du curseur réel.