Author Topic: C#- Binding variables? A better way?  (Read 4644 times)

0 Members and 1 Guest are viewing this topic.

pkohut

  • Guest
C#- Binding variables? A better way?
« on: February 18, 2009, 01:42:33 PM »
Need to create a class derived from TextBox that handles user IO of
latitudes (longitude, angles, etc) strings.  The code below works but
I have to compile with /unsafe, and it sort of follows how I'd approach
this in C++.

So looking for a better or proper way to handle this type of stuff, because
my usual reaction is to check the "allow unsafe code" option.

Let me know if you need more clarification on the code below.

Thanks,
Paul

Code: [Select]
public class CtrlTxtEditLatitude : TextBox
{
...
    public unsafe void BindValue(double * dLatitude)
    {
        _pdLatitude = dLatitude;
        _dLatitude = *dLatitude;
    }

    // this would be called after a the TextBox control looses focus
    protected unsafe void UpdateValue()
    {
        *_pdLatitude = _dLatitude;
    }

    private unsafe double * _pdLatitude; // pointer to variable  decalared in another class
    private double _dLatitude; // working variable
}

public class CtrlView : UserControl
{
...
    public CtrlView()
    {
        try {
            fixed (double * pLatitude = &_latitude)
                _ctrlLatitude.BindValue(pLatitude);
        }
        catch(Exception) {
             // Silverlight throws and exception, just need to catch it.
        }
    }
    public CtrlTxtEditLatitude _ctrlLatitude;
    private double _dLatitude;
}

TonyT

  • Guest
Re: C#- Binding variables? A better way?
« Reply #1 on: February 18, 2009, 04:17:07 PM »
The CLR design strives to eliminate most reasons for having
to use pointers and unsafe code.

I'm not entirely sure if this is what you're looking for but
for simply passing values by-reference the 'out' or 'ref'
parameter modifers can do that.

Code: [Select]

       public class Class1
       {
            public void SetDoubleValue( out double value )
            {
                 value = value * pi;
            }
        }

        public class Class2
        {
             private double radius = 1.0;

             public void Foo()
             {
                   Console.WriteLine( "radius = {0}", radius );
                   Class1.SetDoubleValue( out this.radius );
                   Console.WriteLine( "radius = {0}", radius );
             }
        }


If on the other hand, you are trying to keep a reference to a
field in another object, that problem is most generally handled
by exposing the member/field through a public property, and
then using data binding to reference it.

At its essence, here's how to hold a reference to an object,
and then set/get the value of one of its proerties:

Code: [Select]

      // using System.Reflection;

       public class Class1
       {
            public void SetDoubleValue( object target )
            {
                Type type = target.GetType();
                PropertyInfo prop = type.GetProperty( "Radius ");

                 // get the value of the property for the passed instance:
                 object value = prop.GetValue( target );

                 Console.WriteLine( "Radius = {0}" );

                 // set the value of the property for the passed instance:
                 prop.SetValue( target, pi * (double) value );
            }
        }

        public class Class2
        {
             private double radius = 1.0;

             public double Radius
             {
                  get { return this.radius;}
                  set { this.radius = value;}
             }

             public void Foo()
             {
                  Class1.SetDoubleValue( this );
             }
        }

       

Need to create a class derived from TextBox that handles user IO of
latitudes (longitude, angles, etc) strings.  The code below works but
I have to compile with /unsafe, and it sort of follows how I'd approach
this in C++.

So looking for a better or proper way to handle this type of stuff, because
my usual reaction is to check the "allow unsafe code" option.

Let me know if you need more clarification on the code below.

Thanks,
Paul

Code: [Select]
public class CtrlTxtEditLatitude : TextBox
{
...
    public unsafe void BindValue(double * dLatitude)
    {
        _pdLatitude = dLatitude;
        _dLatitude = *dLatitude;
    }

    // this would be called after a the TextBox control looses focus
    protected unsafe void UpdateValue()
    {
        *_pdLatitude = _dLatitude;
    }

    private unsafe double * _pdLatitude; // pointer to variable  decalared in another class
    private double _dLatitude; // working variable
}

public class CtrlView : UserControl
{
...
    public CtrlView()
    {
        try {
            fixed (double * pLatitude = &_latitude)
                _ctrlLatitude.BindValue(pLatitude);
        }
        catch(Exception) {
             // Silverlight throws and exception, just need to catch it.
        }
    }
    public CtrlTxtEditLatitude _ctrlLatitude;
    private double _dLatitude;
}


pkohut

  • Guest
Re: C#- Binding variables? A better way?
« Reply #2 on: February 18, 2009, 06:18:41 PM »
Thanks Tony.  I think solution 2 is what I'm looking for.  Will try and implement
if just a bit.

The code below isn't tested, but it shows a more complete example of the goal.
In this case the view/form/control etc. creates an instance of ctrlTextEdit...
and calls BindValue with the variable it wants the the text control to update
whenever the user makes changes in the edit box.  Ultimately when the edit
control looses focus then the validated value is placed into bound variable.

I think with what you've shown I'll be able to remove the unsafe keywords and
stick with purely managed code.

Again thanks,
Paul

Code: [Select]
    public partial class ctrlTextEditLatitude : UserControl
    {
        // private bool _validInput = false;
        private unsafe double* _pdLatitude;

        private double _dNewValue;
        private double _dLatitude;
        string _sBefore = "";
        int _nSelPos = -1;
        int _nSelLen = -1;

        public ctrlTextEditLatitude()
        {
            // Required to initialize variables
            InitializeComponent();
            txtBox.TextChanged += new TextChangedEventHandler(txtBox_TextChanged);
            txtBox.LostFocus += new RoutedEventHandler(txtBox_LostFocus);
            txtBox.KeyDown += new KeyEventHandler(txtBox_KeyDown);
            txtBox.GotFocus += new RoutedEventHandler(txtBox_GotFocus);
        }

        void txtBox_GotFocus(object sender, RoutedEventArgs e)
        {
            _dNewValue = _dLatitude;
        }

        void txtBox_KeyDown(object sender, KeyEventArgs e)
        {
            // _validInput = false;
            _sBefore = ((TextBox)sender).Text;
            _nSelPos = ((TextBox)sender).SelectionStart;
            _nSelLen = ((TextBox)sender).SelectionLength;
        }


        void txtBox_TextChanged(object sender, TextChangedEventArgs e)
        {
            TextBox tb = (TextBox)sender;
            double dVal;
            if (IsValidLatitude(tb.Text, out dVal) == false)
            {
                tb.Text = _sBefore;
                tb.SelectionStart = _nSelPos;
                tb.SelectionLength = _nSelLen;
                // _validInput = false;
            }
            else
            {
                // _validInput = true;
                _dNewValue = dVal;
            }
        }

        void txtBox_LostFocus(object sender, RoutedEventArgs e)
        {
            if (_dLatitude != _dNewValue)
                _dLatitude = _dNewValue;

            SetLatitude(_dLatitude);
        }

        public unsafe void SetLatitude(double dLatitude)
        {
            if (dLatitude > 90.0 || dLatitude < -90.0)
                return;

            _dLatitude = dLatitude;
            if (_pdLatitude != null)
            {
                *_pdLatitude = _dLatitude;
                txtBox.Text = ConvertLatitudeDdToDms(_dLatitude, nPrec);
            }
            else
                throw new NotImplementedException();
        }

        public unsafe void BindValue(double* dLatitude)
        {
            _pdLatitude = dLatitude;
            if (_pdLatitude != null)
            {
                *_pdLatitude = _dLatitude;
            }
        }
    }



pkohut

  • Guest
Re: C#- Binding variables? A better way?
« Reply #3 on: February 18, 2009, 07:43:14 PM »
Thanks again Tony, solution 2 worked like a charm.

Paul

TonyT

  • Guest
Re: C#- Binding variables? A better way?
« Reply #4 on: February 18, 2009, 10:06:44 PM »
Thanks again Tony, solution 2 worked like a charm.

Paul

Well, now that you show more of what it is you're doing,
I'll tell you that I wouldn't go about it in  that way, even
if it works.

Before getting into why, first a little tip on custom classes.

In general, events are used primarly by outside consumers,
not by derived types. In most cases, control based classes
have corresponding overridable virtual methods that should
be overridden in derived classes, rather than handling the
corresponding event.

For example, given the events you're using, this would be
considered the officially correct way to handle those same
notifications:

Code: [Select]

namespace WpfControlLibrary1
{
   class LatitutdeTextBox : TextBox
   {
      protected override void OnTextChanged( TextChangedEventArgs e )
      {
         // TODO: Add your implementation here
         base.OnTextChanged( e );
      }

      protected override void OnGotFocus( System.Windows.RoutedEventArgs e )
      {
         // TODO: Add your implementation here
         base.OnGotFocus( e );
      }

      protected override void OnLostFocus( System.Windows.RoutedEventArgs e )
      {
         // TODO: Add your implementation here
         base.OnLostFocus( e );
      }

      protected override void OnKeyDown( System.Windows.Input.KeyEventArgs e )
      {
         // TODO: Add your implementation here
         base.OnKeyDown( e );
      }
   }
}


Insofar as what you're doing, while its certainly possible to use a custom
control to get specially-formatted input like a lat/lon, the disadvantage
of doing it that way is a functional dependence on that custom control.

In .NET, you can use data binding and TypeConverters to implement the
functionality your control provides, in a way that allows it to be used by
any control that's capable of data binding to a property.

You can also use TypeConverters without data binding, by calling them
explicitly from a custom control or from any other code for that matter,
because when the conversion functionality is seperated from control(s)
that use it, it can be easily used wherever it's needed.

In other words, I would expose doubles representing lat & lon values as
properties, and then would write custom TypeConverters that handle
the task of converting between the lat/lon strings and doubles, and then
apply the TypeConverter attributes to the properties, which tells the
framework to use those typeconverters to convert user-entered values
to the native data type, like so:

Code: [Select]

    public class MyGeoLocatedObject
    {
         private double lat;
         private double lon;
       
         [TypeConverter(typeof( LatitudeConverter ))]
         public Latitude
         {
             get
             {
                 return lat;
             }
             set
             {
                 if( value > 360.0 || value < 0.0 )
                    throw new ArgumentException("Value is not in the required range");
                 lat = value;
             }
         }

         [TypeConverter(typeof( LongitudeConverter ))]
         public Longitude
         {
             get
             {
                 return lon;
             }
             set
             {
                 if( value > 180.0 || value < -180.0 )
                    throw new ArgumentException("Value is not in the required range");
                 lon = value;
             }
         }
    }


In the above thoeretical example, LatitudeConverter and LongitudeConverter
are custom classes based on TypeConverter, which perform conversion between
strings and doubles. IOW, they would be responsible for parsing the user entered
strings, validating their correctness and converting them to a double representing
the lat or lon. They would also do the opposite (format the double for display in
the same format it is edited).

The advantages of doing it this way are many. For example, you can
assign an instance of the above class (with the two TypeConverters
implemented) to a PropertyGrid, and the property grid will format the
properties the same way they would be formatted by a custom control,
and the user can also edit the values in the grid it the same format,
and the input will be validated and converted to doubles, without your
having to write a single line of code.

Note that in WPF, TypeConverters are supplemented/superceeded
by ValueConverters, and you can specify a data binding along with a
ValueConverter in XAML.

Here's a more concrete example of a TypeConveter for geo location:

    http://www.codeproject.com/KB/webforms/TypeConverters.aspx

« Last Edit: February 18, 2009, 11:35:20 PM by TonyT »

pkohut

  • Guest
Re: C#- Binding variables? A better way?
« Reply #5 on: February 18, 2009, 11:35:17 PM »
Very helpful reply and it touches on a number of items that I'd search
google for, but just kept getting lost in the chaff.

Paul