webForumDet fria alternativet

skräddarsydda privata events, var när och hur?

.NET

21 svar · 947 visningar · startad av CatZ

Medlem sedan jan. 20022 440 inlägg
Frågan#1

Eftersom vi tydligen har i alla fall 4 stycken extremt duktiga utvecklare i den här tråden så kan ni ju svara på den här frågan gällande att hantera händelser i en klass. Jag har fält som heter width, length och thickness. Dessa ska få ett värde tilldelat sig när Widths, Lengths och Thicknesses får ett värde. Hur ska jag hantera det korrekt? Det är fält som inte ska inte sparas i någon databas utan bara användas för att räkna ut volym och lite annat. Sedan behöver jag ju en metod för att räkna ut värdena. Jag har nu suttit och snorat över det här en stund och får inte riktigt till någon lösning som jag känner blir tillräckligt "snygg" designmässigt. Kollegan vill att jag räknar ut allting i Form1.cs vilket jag givetvis vägrar.

Jag är rätt basic på det här ämnet och nu ska allting vara jäkligt privat. Jag vill tex inte i Form1.cs behöva sätta en

value.OnValueChanged +=

eftersom det ska skötas helt backend. Får inte ihop det på något smidigt sätt.

Medlem sedan sep. 200888 inlägg
#2

Du skulle nog kunna göra så att du i alla dina set när du satt nya värdet anropar en metod som läser av klassesn tillstånd, räknar om värderna och sätter om dem.
Sen ha ett event på klassen för att säga till koden utanför ex formet att nu har värderna ändras läs om och visa den nya. Typ. Är det nått sånt du vill åt?

Medlem sedan jan. 20022 440 inlägg
#3

Ja precis, det var väl en bättre förklaring på vad jag var ute efter.

Medlem sedan sep. 200888 inlägg
#4

Då skulle jag skapar event och delegate på min entiet.
Sen skulle jag skapa en metod som är privat (om man inte vill räkna om den när som, då publik.) metoden ligger självklart i entiteten (klassen) den läser helt enkelt in propparna och räknar om nya värden som den sätter om. När denne metod körts triggar den bara eventmetoden som då form antagligen lyssnar på och vet att nu har nått hänt.

Medlem sedan jan. 20022 440 inlägg
#5

Såhär ser ett utkast av klassen ut

public partial class Packages
{
	private int length { get; set; }
	private int width { get; set; }
	private int thickness { get; set; }

	public string Lengths { get; set; }
	public string Widths { get; set; }
	public string Thicknesses { get; set; }

	public decimal Volume { get; set; }
	public decimal RunningMetre { get; set; }
	public decimal Stuage { get; set; }

}

Widths, Lengths, Thicknesses är en "range" av tillåtna värden för det som finns i paketet. Att välja rätt där är ju inga problem men jag vill ha resten av logiken på samma ställe osynligt för den som använder klassen. Så jag behöver då några privata event i klassen för att sätta rätt värden osv. Length, Width och Thickness kommer aldrig användas annat än till beräkning och sparas heller ingenstans.

Medlem sedan jan. 20022 440 inlägg
#6

Först blir det ju att skapa några delegates och events typ

private delegate void WidthsChangedEventHandler(string newWidths);
private event WidthsChangedEventHandler WidthsChanged;

Sedan måste jag ju hantera eventet i set på Widths med något i stil med

		public string Widths
		{
			get { return this.Widths; }
			set
			{
				this.Widths = value;
				if (this.WidthsChanged != null)
				{
					this.WidthsChanged(value);
				}
					
			}
		}

men hur jag ska få ut det ena värdet som en int och sätta till width övergår mitt förstånd men så är jag rejält krasslig idag också...

Medlem sedan jan. 20022 440 inlägg
#7

det jag skulle vilja göra är något som typ

		public Packages()
		{
			this.width += new WidthsChangedEventHandler(this.Widths);
		}

i konstruktorn... kan man göra på något sådant sätt?

Medlem sedan aug. 20003 575 inlägg
#8

Hmmm, måste du ha det som en sträng? Kan du inte representera en range som ett Range objekt?

		public string Widths
		{
			get { return this.Widths; }
			set
			{
				this.Widths = value;
				if (this.WidthsChanged != null)
				{
					this.WidthsChanged(value);
				}
					
			}
		}

Ser ut som du anropar din autoproperty som finns i din klass med exakt samma namn. När man inte har nytta av autoproperty som du egentligen inte har eftersom du vill lägga in funktionalitet så tycker jag att man nog skall göra ett vanligt field

private string widths;

Hmm. annars hänger jag inte riktigt med exakt vad du vill göra kanske är lättare med lite mer beskrivning.

Medlem sedan aug. 20003 575 inlägg
#9

CatZ skrev:

det jag skulle vilja göra är något som typ

		public Packages()
		{
			this.width += new WidthsChangedEventHandler(this.Widths);
		}

i konstruktorn... kan man göra på något sådant sätt?

Det du är ute efter går nog att lösa om du tar emot object.

Medlem sedan jan. 20022 440 inlägg
#10

Ett paket innehåller olika längder, bredder och höjder. Det exakta innehållet i paketet sparas inte (fråga inte mig varför, när jag ställde den frågan vidare så undrade de andra det också.). För att på en höft få fram volym osv behöver vi hämta min-max värdena från sorterings tabellen. De ser ut som "18-54" och sparas som varchar.

Nu vill jag då på ett cleant sett ta det ena värdet 18 eller 54 och räkna ut volymen. Jag kan komma på ett flertal olika fullösningar och hacks men inget som känns riktigt bra. Jag har blivit helt frälst i att lägga för mycket tid på "snygg" kod tyvärr. Efter allt tjat om att göra rätt i andra trådar så tänkte jag att jag skulle slänga ihop något vackert istället för att fortsätta med fullösningar. ;)

Medlem sedan aug. 20003 575 inlägg
#11

Okej, men en Range klass tycker jag ändå att du skall ha, även om det sparas som en varchar.

Möjligtvis att det skall till lite events på denna också.
Samt att man kanske skall kunna sätta

NumberRange numberRange = "10-20"; och få allting satt...

public class NumberRange
{
   public int Left { get; set; }
   public int Right { get; set; }

   public override string ToString()
   {
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.Append(Left.ToString());
        stringBuilder.Append(" - ");
        stringBuilder.Append(Right.ToString());

        return stringBuilder.ToString();
}
Medlem sedan jan. 20022 440 inlägg
#12

Ah nu vaknade jag till usch vad efterbliven man är när man är förkyld! Självklart har du rätt Nickemannen! Det var precis den snygga lösningen jag letade efter. Jag vågar inte berätta vilka andra saker jag INTE tänkt på idag hehe

Medlem sedan jan. 20023 327 inlägg
#13

En liten detalj...

   
public override string ToString()
{
    StringBuilder stringBuilder = new StringBuilder();
    stringBuilder.Append(Left.ToString());
    stringBuilder.Append(" - ");
    stringBuilder.Append(Right.ToString());

    return stringBuilder.ToString();
}

skulle kunna effektiviseras och kortas ner till:

   
public override string ToString()
{
    return string.Concat(this.Left, " - ", this.Right);
}
Medlem sedan maj 20012 812 inlägg
#14

Här är hur jag skulle byggt det hela.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace BL.RetailSystem.Application.Shared.Entity
{
    public class MyClass
    {
        #region -- Declaration
        private decimal _Width;
        private decimal _Length;
        private decimal _Depth;

        public event SizeChangedEventHandler SizeChanged;
        #endregion

        #region -- Properties
        public decimal Width
        {
            get { return _Width; }
            set { 
                _Width = value;
                OnSizeChanged();
            }
        }
        public decimal Length
        {
            get { return _Length; }
            set { 
                _Length = value;
                OnSizeChanged();
            }
        }
        public decimal Depth
        {
            get { return _Depth; }
            set { 
                _Depth = value;
                OnSizeChanged();
            }
        }

        public decimal Volym
        {
            get { return CalculateVolym(); }
        }
        #endregion

        #region -- Methods

        private void OnSizeChanged()
        {
            if (SizeChanged != null)
                SizeChanged();
        }
        private decimal CalculateVolym()
        {
            return this.Width * this.Length * this.Depth;
        }
        #endregion
    }
}

Det kräver dock att du lyssnar på eventet SizeChanged från din entitet i din winform, så du kan ändra det värdet sedan. Antingen så som här att du frågar entiteten efter dess olika properties, eller så ändrar du i SizeChangedEventArgs och skickar med de olika värdena där. Vilket fall som helst så kräver det kod av dig att "binda om" dina värden.

Så därför har jag ett annat förslag till dig, och detta kommer kännas väldigt bakvänt till en början, men oj så smidigt det är. Och det är att använda databinding för att bind din properties på dina entiteter till olika fält i din winform. Så du binder helt enkelt propertyen volymn till ett textfält och när du sen ändrar på någon av din property width, length, depth. Så triggar du igång ett NotifyPropertyEvent för din volymn och vips så har värdet ändrats i ditt textfält.

Det är riktigt smidigt, även om det är flera gånger bättre i WPF än i Winform så fungerar det hyfsat bra i winform.

Ta och läs på information om Databindings och speciellt följande interface: INotifyPropertyChanged, IDataErrorInfo, IEditableObject. Jag har en basklass för mina entiteter som använder sig av detta som ser ut så här (johan blunda nu!!!)

- M

Medlem sedan maj 20012 812 inlägg
#15
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
using System.Reflection;
using System.Collections;
using System.Runtime.Serialization;
using System.Xml.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;

namespace TS.Classes
{
    [DataContract(Name = "baseentity", Namespace = "http://classes.gladh.org")]
    [Serializable]
    [XmlType(TypeName = "baseentity", Namespace = "http://classes.gladh.org/")]
    public class BaseEntity : INotifyPropertyChanged, IDataErrorInfo, ICloneable, IEditableObject
    {
        #region -- declaration
        [field: NonSerialized]
        private event PropertyChangedEventHandler PropertyChanged;
        
        //-- Holds information about the errors added 
        [field: NonSerialized]
        private Dictionary<string, string> _ErrorDictonary = new Dictionary<string, string>();
        [field: NonSerialized]
        private bool _HasNoErrors = true;

        //-- Holds information about all changes to proprerties in this object.
        [field: NonSerialized]
        private Hashtable _Propreties;
        
        [field: NonSerialized]
        private bool _IsEdit = false;
        #endregion

        #region -- Properties
        [EditablePropertyNotSupported]
        public bool IsEdit
        {
            get { return _IsEdit; }
            set { _IsEdit = value; }
        }
        [EditablePropertyNotSupported]
        private string Error
        {
            get
            {
                if (_ErrorDictonary.Count != 0)
                    return "An error has occured";
                else
                    return string.Empty;
            }
        }

        protected bool HasNoErrors
        {
            get { return _HasNoErrors; }
            set { _HasNoErrors = value; }
        }
        protected string this[string columnName]
        {
            get
            {
                if (_ErrorDictonary == null)
                    _ErrorDictonary = new Dictionary<string, string>();

                if (_ErrorDictonary.ContainsKey(columnName))
                    return _ErrorDictonary[columnName];
                else
                    return string.Empty;
            }
            set
            {
                if (_ErrorDictonary == null)
                    _ErrorDictonary = new Dictionary<string, string>();

                if (_ErrorDictonary.ContainsKey(columnName))
                    if (string.IsNullOrEmpty(value))
                        _ErrorDictonary.Remove(columnName);
                    else
                        _ErrorDictonary[columnName] = value;
                else
                    _ErrorDictonary.Add(columnName, value);
            }
        }
        #endregion

        #region -- Methods
        public void NotifyPropertyChanged(String info)
        {
            NotifyPropertyChanged(info, true);
        }
        public void NotifyPropertyChanged(String info, bool fieldEdit)
        {
            if(fieldEdit) this.IsEdit = true;

            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(info));
        }
        /// <summary>
        /// When using the Binding object in .NET we need a way to handle changes to our entites
        /// </summary>
        public virtual void BeginEdit()
        {
            this.IsEdit = false;

            PropertyInfo[] properties = (this.GetType()).GetProperties(BindingFlags.Public | BindingFlags.Instance);
            _Propreties = new Hashtable(properties.Length - 1);

            for (int i = 0; i < properties.Length; i++)
            {
                object[] found = properties[i].GetCustomAttributes(typeof(EditablePropertyNotSupportedAttribute), true);
                if (found != null && found.Length != 0)
                    continue;

                if (null != properties[i].GetSetMethod())
                {
                    Type cloneableType = properties[i].PropertyType.GetInterface("ICloneable", true);

                    object value = null;
                    if (cloneableType != null)
                    {
                        ICloneable cloneable = properties[i].GetValue(this, null) as ICloneable;
                        if (cloneable != null)
                            value = cloneable.Clone();
                        else
                            value = properties[i].GetValue(this, null);
                    }
                    else
                        value = properties[i].GetValue(this, null);

                    _Propreties.Add(properties[i].Name, value);
                }
            }
        }
        /// <summary>
        /// When cancel an entity that has a Binding, we need to be able to set the old value back.
        /// </summary>
        public virtual void CancelEdit()
        {
            if (null == _Propreties)
                return;

            PropertyInfo[] properties = (this.GetType()).GetProperties(BindingFlags.Public | BindingFlags.Instance);
            for (int i = 0; i < properties.Length; i++)
            {
                if (null != properties[i].GetSetMethod() && _Propreties.ContainsKey(properties[i].Name))
                {
                    object value = _Propreties[properties[i].Name];
                    properties[i].SetValue(this, value, null);
                }
            }

            EndEdit();
        }
        /// <summary>
        /// Clean up after us when an edit is completed.
        /// </summary>
        public virtual void EndEdit()
        {
            _Propreties = null;
            this.IsEdit = false;
        }

        /// <summary>
        /// Create a deep copy of this object
        /// </summary>
        /// <remarks>
        /// <para>
        /// We use the serialization to perfome the DeepCopy, that means that the object needs to have the Serialized attribute sat on it, or implement the ISerializeble interface
        /// </para>
        /// </remarks>
        /// <returns></returns>
        public virtual object Clone()
        {
            if (this is ISerializable)
                throw new NotSupportedException("The object that shall be cloned need to implement the ISerializable interface");

            BinaryFormatter binaryFormatter = new BinaryFormatter();
            MemoryStream memoryStream = new MemoryStream();
            binaryFormatter.Serialize(memoryStream, this);

            memoryStream.Position = 0;
            return binaryFormatter.Deserialize(memoryStream);
        }
        public virtual bool Validate() { return true; }
        
        protected virtual void ClearError(string propertyName)
        {
            ClearError(propertyName, true);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <summary>
        /// Clear any errormessage that may be set on the property
        /// </summary>
        /// <param name="propertyName"></param>
        protected virtual void ClearError(string propertyName, bool isEdit)
        {
            this[propertyName] = string.Empty;
            this.NotifyPropertyChanged(propertyName, isEdit);
        }
        /// <summary>
        /// Defines error on diffrent properties for the entity
        /// </summary>
        /// <remarks>
        /// <para>
        /// this metod will raise an NotifyPropertyChanged() event for the propertyName, to let the UI know something has happend with this property
        /// </para>
        /// </remarks>
        /// <param name="propertyName"></param>
        /// <param name="errorMessage"></param>
        protected virtual void SetError(string propertyName, string errorMessage)
        {
            this.HasNoErrors = false;
            this[propertyName] = errorMessage;
            this.NotifyPropertyChanged(propertyName, false);
        }
        #endregion

        #region -- Constructor
        public BaseEntity() { }
        #endregion

        #region -- INotifyPropertyChanged Implementation
        event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged
        {
            add { PropertyChanged += value; }
            remove { PropertyChanged -= value; }
        }
        #endregion

        #region -- IDataErrorInfo Members
        string IDataErrorInfo.Error
        {
            get { return this.Error; }
        }
        string IDataErrorInfo.this[string columnName]
        {
            get
            {
                return this[columnName];
            }
        }
        #endregion

        #region -- ICloneable Members
        object ICloneable.Clone()
        {
            return Clone();
        }
        #endregion

        #region IEditableObject Members
        void IEditableObject.BeginEdit()
        {
            BeginEdit();
        }
        void IEditableObject.CancelEdit()
        {
            CancelEdit();
        }
        void IEditableObject.EndEdit()
        {
            EndEdit();
        }
        #endregion
    }
}

Så hem och lek lite på kvällen med databindings så kan du föreslå att ni skriver om hela koden efter helgen :)

- M

Medlem sedan sep. 200888 inlägg
#16

Asså snälla.. GE upp regioner... mår illa! ;-)

usch...

Medlem sedan maj 20012 812 inlägg
#17

Johan du skulle ju blunda...

Dessutom så måste du ju älska regioner om du har så många metoder som du "skryter om" :)..

Skulle få frispel på alla metoder om jag inte fick gömma dem med regioner....

- M

Medlem sedan sep. 200888 inlägg
#18

Gladh skrev:

Johan du skulle ju blunda...

Dessutom så måste du ju älska regioner om du har så många metoder som du "skryter om" :)..

Skulle få frispel på alla metoder om jag inte fick gömma dem med regioner....

- M

haha... nja så många blir det ju inte. :-)
Det blir nästan färre då det blir mindre kod. Har rätt små klasser oxå. Blir lite så automatiskt. Så svårt att förklara i text bara varför.

Men ang regioner. Varför? det är så störigt att öppna o stänga kod hela tiden, sen gömmer man ju massa viktig info... nä aldrig förstått mig på sånt. det är som att vika massa sidor åt olika håll i en bok så måste man vika upp dem för att få läsa boken. :birp

Dock kan det va ok när man har dmeokod. bara för att folk skall fokusera på den kod man vill dema och inte kod runtomkring...

En gammal kollega till mig skrev en helt ok blogpost om sin frustration ang regioner:

http://softwarearia.com/2007/11/22/TheCurseOfRegions.aspx

Rätt kul läsning... ! :bire

Medlem sedan jan. 20023 327 inlägg
#19

johannormen skrev:

Men ang regioner. Varför? det är så störigt att öppna o stänga kod hela tiden, sen gömmer man ju massa viktig info... nä aldrig förstått mig på sånt. det är som att vika massa sidor åt olika håll i en bok så måste man vika upp dem för att få läsa boken. :birp

Tycker det är bra med regioner så länge som man inte missbrukar det. Brukar hålla mig till något i stil med:

  • Using Directives
  • Fields
  • Constructor(s)
  • Properties
  • Methods

Tycker det är skönt att kunna minimera det jag inte vill se när arbetar i en klass. Så länge man håller sig till ett lägre antal och tydligt definierade regioner så tycker jag det är okej.

Medlem sedan sep. 200888 inlägg
#20

Compusa skrev:

Tycker det är bra med regioner så länge som man inte missbrukar det. Brukar hålla mig till något i stil med:

  • Using Directives
  • Fields
  • Constructor(s)
  • Properties
  • Methods

Tycker det är skönt att kunna minimera det jag inte vill se när arbetar i en klass. Så länge man håller sig till ett lägre antal och tydligt definierade regioner så tycker jag det är okej.

Men varför vill du gömma delar? du vill ju se vad din kod gör? Scrolla upp o ner brukar fungera ganska bra när man läser kod... Kod skall läsas som en uppsatts inte som en en dynamisk berättelse där man måste dra i ett dragspelspapper för att förstå vad som händer.

Regioner gömmer bara viktig information för dig och andra. Främst är det andra som kommer lida mest och du själv i framtiden när du går tillbaka till legazy kod och gärna vill snabbt förstå vad koden gör. då vill du inte sitta och expandera massa regioner, du vill snabbt se flödet i din kod.

Allra minst gör det ingen nytta att gömma proppar om du ändå måste trycka fram dem...

Säg inte nu men man kan expandera alla med en gång... Jepp men varför skall man då ha dem?

:bire

273 ms totalt · 4 externa anrop · v20260731065814-full.86ec41c2
125 ms — deklarationer (db)
0 ms — hämta statistik (cache)
141 ms — hämta tråd, inlägg och bilagor (db)
126 ms — ändringar (db)