Newsgroup: it.comp.dotnet
Titolo: [WPF] DataGrid con ComboBox

Ciao a tutti,
dopo aver googlato invano per due giorni in cerca di una soluzione, vi 
pongo il mio problema attuale:
ho una finestra di popup che contiene una datagrid da popolare con 
colonne di numero variabile, per cui chi conosce il componente in 
questione sa che, a meno di magheggi più o meno complessi, le colonne 
vanno aggiunte nel code behind perché non si possono bindare in scrittura.
Detto questo, usando delle colonne con combobox (DataGridComboBoxColumn) 
non riesco ad agganciare la selezione con l'aggiornamento della property 
bindata del viewmodel:
[lato xaml]:
     <Popup x:Name="EmulatorPopup">
         <DataGrid
		x:Name="BackendEmulatorDatagrid"
		AutoGenerateColumns="False"
		ItemsSource="{Binding EmulationRows}">
         </DataGrid>
     </Popup>
[lato viewmodel]:
     public class BackendEmulatorViewModel : INotifyPropertyChanged
     {
         public event PropertyChangedEventHandler PropertyChanged;
         private void OnPropertyChanged(string propertyName)
         {
             if (PropertyChanged != null)
                 PropertyChanged(this, new 
PropertyChangedEventArgs(propertyName));
         }
         private ObservableCollection<EmulationRow> _emulationRows;
         public ObservableCollection<EmulationRow> EmulationRows
         {
             get
             {
                 return _emulationRows;
             }
             set
             {
                 if (_emulationRows != value)
                 {
                     _emulationRows = value;
                     OnPropertyChanged("EmulationRows");
                 }
             }
         }
         private List<EmulationColumn> _socketList;
         public List<EmulationColumn> SocketList
         {
             get
             {
                 return _socketList;
             }
             set
             {
                 if (_socketList != value)
                 {
                     _socketList = value;
                     OnPropertyChanged("SocketList");
                 }
             }
         }
         private static List<string> _sendSockets;
         private static List<string> _receiveSockets;
         public BackendEmulatorViewModel(List<string> sendSockets, 
List<string> receiveSockets)
         {
             _sendSockets = sendSockets;
             _receiveSockets = receiveSockets;
             _emulationRows = new ObservableCollection<EmulationRow>();
             _socketList = new List<EmulationColumn>();
             foreach (string s in _sendSockets)
             {
                 _socketList.Add(new EmulationColumn() { Header = s, 
SocketDirection = Direction.Send });
             }
             foreach (string r in _receiveSockets)
             {
                 _socketList.Add(new EmulationColumn() { Header = r, 
SocketDirection = Direction.Receive });
             }
             foreach (EmulationColumn ec in _socketList)
             {
                 ec.AvailableValues = new List<string>();
 
ec.AvailableValues.Add(ResponseEditorStrings.BackendEmulatorUndefinedLabel);
                 if (ec.SocketDirection == Direction.Send)
                 {
 
ec.AvailableValues.Add(ResponseEditorStrings.BackendEmulatorElseLabel);
 
ec.AvailableValues.Add(ResponseEditorStrings.BackendEmulatorUnmatchedLabel);
                 }
             }
         }
         public enum Direction { Send, Receive };
         public class EmulationColumn : INotifyPropertyChanged
         {
             public event PropertyChangedEventHandler PropertyChanged;
             private void OnPropertyChanged(string propertyName)
             {
                 if (PropertyChanged != null)
                     PropertyChanged(this, new 
PropertyChangedEventArgs(propertyName));
             }
             public Direction SocketDirection { get; set; }
             public string Header { get; set; }
             public List<string> AvailableValues { get; set; }
             private string _selectedValue;
             public string SelectedValue
             {
                 get
                 {
                     return _selectedValue;
                 }
                 set
                 {
                     _selectedValue = value;
                     OnPropertyChanged("SelectedValue");
                 }
             }
         }
         public class EmulationRow : INotifyPropertyChanged
         {
             public event PropertyChangedEventHandler PropertyChanged;
             private void OnPropertyChanged(string propertyName)
             {
                 if (PropertyChanged != null)
                     PropertyChanged(this, new 
PropertyChangedEventArgs(propertyName));
             }
             private List<string> _cellValues;
             public List<string> CellValues
             {
                 get { return _cellValues; }
                 set
                 {
                     _cellValues = value;
                     OnPropertyChanged("CellValues");
                 }
             }
         }
     }
[lato code behind]:
         public BackendEmulationEditor()
         {
             InitializeComponent();
             EmulatorPopup.Opened += new EventHandler(EmulatorPopup_Opened);
         }
         void EmulatorPopup_Opened(object sender, EventArgs e)
         {
             //la costruzione manuale delle colonne è dovuta al fatto 
che DataGrid.Columns non è bindabile
             var bevm = (sender as FrameworkElement).DataContext as 
BackendEmulatorViewModel;
             if (bevm != null)
             {
                 BackendEmulatorDatagrid.Columns.Clear();
                 foreach 
(SpeechVillage.SpeechDraw.ResponseEditor.ViewModels.BackendEmulatorViewModel.EmulationColumn 
ec in bevm.SocketList)
                 {
                     DataGridComboBoxColumn dgcbc = new 
DataGridComboBoxColumn()
                     {
                         Header = ec.Header,
                         ItemsSource = ec.AvailableValues,
                         SelectedItemBinding = new Binding("SelectedValue")
//altro tentativo:
//SelectedValueBinding = new Binding("SelectedValue"),
                         //SelectedValuePath = ec.SelectedValue,
                         //DisplayMemberPath = ec.SelectedValue
                     };
                     BackendEmulatorDatagrid.Columns.Add(dgcbc);
                 }
             }
         }
(ho seguito questo link:
http://blogs.msdn.com/b/vinsibal/archive/2008/10/31/wpf-datagrid-datagridcomboboxcolumn-v1-intro.aspx
)
purtroppo la selezione delle combobox non passa per il set di 
SelectedValue, da cui capisco che il binding non va a buon fine.
La cosa strana è che non funziona neanche la solution scaricata dal sito 
di cui sopra, per cui non capisco cosa c'è che non va, anche se credo 
che la chiave del problema sia nella connessione tra combobox e property 
(non) bindata.
C'è qualcuno che può aiutarmi? Grazie

Il 08/06/2011 15:12, marcosabba ha scritto:
Aggiungo un'informazione:
se inserisco un converter vuoto:
         void EmulatorPopup_Opened(object sender, EventArgs e)
         {
             //la costruzione manuale delle colonne è dovuta al fatto 
che DataGrid.Columns non è bindabile
             var bevm = (sender as FrameworkElement).DataContext as 
BackendEmulatorViewModel;
             if (bevm != null)
             {
                 BackendEmulatorDatagrid.Columns.Clear();
                 foreach 
(SpeechVillage.SpeechDraw.ResponseEditor.ViewModels.BackendEmulatorViewModel.EmulationColumn 
ec in bevm.SocketList)
                 {
                     Binding binding = new Binding("SelectedValue");
                     binding.Converter = new EmptyConverter();
                     DataGridComboBoxColumn dgcbc = new 
DataGridComboBoxColumn()
                     {
                         Header = ec.Header,
                         ItemsSource = ec.AvailableValues,
                         SelectedItemBinding = binding,
                         //SelectedValueBinding = binding,
                         //SelectedValuePath = ec.SelectedValue,
                         //DisplayMemberPath = ec.SelectedValue
                     };
                     BackendEmulatorDatagrid.Columns.Add(dgcbc);
                 }
                 BackendEmulatorDatagrid.CanUserSortColumns = false;
                 BackendEmulatorDatagrid.ItemsSource = bevm.EmulationRows;
             }
         }
noto che il binding con la property SelectedValue in qualche modo è 
avvenuto, passa per la ConvertBack del converter e mostra come valore la 
stringa selezionata nella combobox. Poi però l'assegnamento del valore 
alla property non va a buon fine (eccezione NullReferenceException con 
questa StackTrace:
    at 
System.Windows.Data.BindingExpression.IsValidValueForUpdate(Object 
value, Type sourceType)
    at System.Windows.Data.BindingExpression.ConvertProposedValue(Object 
value)
    at 
System.Windows.Data.BindingExpression.ValidateAndConvertProposedValue(Collection`1& 
values)
    at 
System.Windows.Controls.DataGridHelper.ValidateWithoutUpdate(FrameworkElement 
element)
    at 
System.Windows.Controls.DataGridColumn.CommitCellEdit(FrameworkElement 
editingElement)
    at 
System.Windows.Controls.DataGridColumn.CommitEdit(FrameworkElement 
editingElement)
    at System.Windows.Controls.DataGridCell.CommitEdit()
    at 
System.Windows.Controls.DataGrid.OnExecutedCommitEdit(ExecutedRoutedEventArgs 
e)
    at System.Windows.Controls.DataGrid.OnExecutedCommitEdit(Object 
sender, ExecutedRoutedEventArgs e)
    at System.Windows.Input.CommandBinding.OnExecuted(Object sender, 
ExecutedRoutedEventArgs e)
    at System.Windows.Input.CommandManager.ExecuteCommandBinding(Object 
sender, ExecutedRoutedEventArgs e, CommandBinding commandBinding)
    at 
System.Windows.Input.CommandManager.FindCommandBinding(CommandBindingCollection 
commandBindings, Object sender, RoutedEventArgs e, ICommand command, 
Boolean execute)
    at System.Windows.Input.CommandManager.FindCommandBinding(Object 
sender, RoutedEventArgs e, ICommand command, Boolean execute)
    at System.Windows.Input.CommandManager.OnExecuted(Object sender, 
ExecutedRoutedEventArgs e)
    at System.Windows.UIElement.OnExecutedThunk(Object sender, 
ExecutedRoutedEventArgs e)
    at 
System.Windows.Input.ExecutedRoutedEventArgs.InvokeEventHandler(Delegate 
genericHandler, Object target)
    at System.Windows.RoutedEventArgs.InvokeHandler(Delegate handler, 
Object target)
    at System.Windows.RoutedEventHandlerInfo.InvokeHandler(Object 
target, RoutedEventArgs routedEventArgs)
    at System.Windows.EventRoute.InvokeHandlersImpl(Object source, 
RoutedEventArgs args, Boolean reRaised)
    at System.Windows.UIElement.RaiseEventImpl(DependencyObject sender, 
RoutedEventArgs args)
    at System.Windows.UIElement.RaiseEvent(RoutedEventArgs args, Boolean 
trusted)
    at System.Windows.Input.RoutedCommand.ExecuteImpl(Object parameter, 
IInputElement target, Boolean userInitiated)
    at System.Windows.Input.RoutedCommand.Execute(Object parameter, 
IInputElement target)
    at System.Windows.Controls.DataGrid.EndEdit(RoutedCommand command, 
DataGridCell cellContainer, DataGridEditingUnit editingUnit, Boolean 
exitEditMode)
    at System.Windows.Controls.DataGrid.CommitAnyEdit()
    at System.Windows.Controls.DataGrid.OnEnterKeyDown(KeyEventArgs e)
    at System.Windows.Controls.DataGrid.OnKeyDown(KeyEventArgs e)
    at System.Windows.UIElement.OnKeyDownThunk(Object sender, 
KeyEventArgs e)
    at System.Windows.Input.KeyEventArgs.InvokeEventHandler(Delegate 
genericHandler, Object genericTarget)
    at System.Windows.RoutedEventArgs.InvokeHandler(Delegate handler, 
Object target)
    at System.Windows.RoutedEventHandlerInfo.InvokeHandler(Object 
target, RoutedEventArgs routedEventArgs)
    at System.Windows.EventRoute.InvokeHandlersImpl(Object source, 
RoutedEventArgs args, Boolean reRaised)
    at System.Windows.UIElement.RaiseEventImpl(DependencyObject sender, 
RoutedEventArgs args)
    at System.Windows.UIElement.RaiseTrustedEvent(RoutedEventArgs args)
    at System.Windows.UIElement.RaiseEvent(RoutedEventArgs args, Boolean 
trusted)
    at System.Windows.Input.InputManager.ProcessStagingArea()
    at System.Windows.Input.InputManager.ProcessInput(InputEventArgs input)
    at System.Windows.Input.InputProviderSite.ReportInput(InputReport 
inputReport)
    at 
System.Windows.Interop.HwndKeyboardInputProvider.ReportInput(IntPtr 
hwnd, InputMode mode, Int32 timestamp, RawKeyboardActions actions, Int32 
scanCode, Boolean isExtendedKey, Boolean isSystemKey, Int32 virtualKey)
    at 
System.Windows.Interop.HwndKeyboardInputProvider.ProcessKeyAction(MSG& 
msg, Boolean& handled)
    at 
System.Windows.Interop.HwndSource.CriticalTranslateAccelerator(MSG& msg, 
ModifierKeys modifiers)
    at System.Windows.Interop.HwndSource.OnPreprocessMessage(Object param)
    at 
System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate 
callback, Object args, Int32 numArgs)
    at MS.Internal.Threading.ExceptionFilterHelper.TryCatchWhen(Object 
source, Delegate method, Object args, Int32 numArgs, Delegate catchHandler)
    at System.Windows.Threading.Dispatcher.InvokeImpl(DispatcherPriority 
priority, TimeSpan timeout, Delegate method, Object args, Int32 numArgs)
    at System.Windows.Threading.Dispatcher.Invoke(DispatcherPriority 
priority, Delegate method, Object arg)
    at System.Windows.Interop.HwndSource.OnPreprocessMessageThunk(MSG& 
msg, Boolean& handled)
    at 
System.Windows.Interop.HwndSource.WeakEventPreprocessMessage.OnPreprocessMessage(MSG& 
msg, Boolean& handled)
    at System.Windows.Interop.ThreadMessageEventHandler.Invoke(MSG& msg, 
Boolean& handled)
    at 
System.Windows.Interop.ComponentDispatcherThread.RaiseThreadMessage(MSG& 
msg)
    at System.Windows.Threading.Dispatcher.PushFrameImpl(DispatcherFrame 
frame)
    at System.Windows.Threading.Dispatcher.PushFrame(DispatcherFrame frame)
    at System.Windows.Threading.Dispatcher.Run()
    at System.Windows.Application.RunDispatcher(Object ignore)
    at System.Windows.Application.RunInternal(Window window)
    at System.Windows.Application.Run(Window window)
    at System.Windows.Application.Run()
    at SpeechVillage.SpeechDraw.SpeechDraw.Main() in 
D:\Documenti\SpeechVillageProjects\Visual Studio 
2010\Projects\SD3\src\XXXXXX\obj\x86\Debug\XXXXXX.g.cs:line 50
    at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, 
String[] args)
    at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence 
assemblySecurity, String[] args)
    at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
    at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
    at System.Threading.ExecutionContext.Run(ExecutionContext 
executionContext, ContextCallback callback, Object state, Boolean 
ignoreSyncCtx)
    at System.Threading.ExecutionContext.Run(ExecutionContext 
executionContext, ContextCallback callback, Object state)
    at System.Threading.ThreadHelper.ThreadStart()
)

Ciao marcosabba,

io non ho capito :-)
puoi dettagliare meglio come gira il fumo nello xaml?
.m
-- 
Mauro Servienti
{C67C0157-5D98-4733-A75E-93CAEE4BADC8}
Microsoft MVP - Visual C# / MCTS
http://mvp.support.microsoft.com/profile/mauro
blog @ 
http://milestone.topics.it
whynot [ at ] topics [ dot ] it

 puoi dettagliare meglio come gira il fumo nello xaml?
Ciao Mauro, intanto grazie.
Chiedo scusa in anticipo se il post è "lungo" ma inserisco più dettagli 
possibile.
Lato xaml c'è ben poca roba, visto che il tutto lo butto dentro lato 
code behind. In soldoni,
[lato xaml]:
      <Popup x:Name="EmulatorPopup">
          <DataGrid
         x:Name="BackendEmulatorDatagrid"
         AutoGenerateColumns="False">
          </DataGrid>
      </Popup>
[lato code behind del popup]:
public partial class BackendEmulationEditor : UserControl
{
     public BackendEmulationEditor()
     {
         InitializeComponent();
         EmulatorPopup.Opened += new EventHandler(EmulatorPopup_Opened);
     }
     void EmulatorPopup_Opened(object sender, EventArgs e)
     {
         //la costruzione manuale delle colonne è dovuta al fatto che 
DataGrid.Columns non è bindabile
         var bevm = (sender as FrameworkElement).DataContext as 
BackendEmulatorViewModel;
         if (bevm != null)
         {
             BackendEmulatorDatagrid.Columns.Clear();
             foreach 
(XXXX.YYYY.ResponseEditor.ViewModels.BackendEmulatorViewModel.EmulationColumn 
ec in bevm.SocketList)
             {
                 Binding binding = new Binding("SelectedVal");
                 binding.Converter = new EmptyConverter();
                 DataGridComboBoxColumn dgcbc = new 
DataGridComboBoxColumn()
                 {
                     Header = ec.Header,
                     ItemsSource = ec.AvailableValues,
                     //SelectedItemBinding = binding,
                     SelectedValuePath = ec.SelectedVal,
                     SelectedValueBinding = binding,
                     //DisplayMemberPath = ec.SelectedValue
                 };
                 BackendEmulatorDatagrid.Columns.Add(dgcbc);
             }
             BackendEmulatorDatagrid.CanUserSortColumns = false;
             BackendEmulatorDatagrid.ItemsSource = bevm.EmulationRows;
         }
     }
}
i settaggi sul componente DataGrid come vedi li faccio tramite 
BackendEmulatorDatagrid sul gestore dell'evento Opened, lì imposto 
l'ItemsSource della DataGrid, cioè la collection delle righe. Il view 
model te lo riscrivo qui sotto:











         private string _selectedVal;
         public string SelectedVal
         {
             get
             {
                 return _selectedVal;
             }
             set
             {
                 _selectedVal = value;
                 OnPropertyChanged("SelectedVal");
             }
         }
     }
     public class EmulationRow : INotifyPropertyChanged
     {
         public event PropertyChangedEventHandler PropertyChanged;
         private void OnPropertyChanged(string propertyName)
         {
             if (PropertyChanged != null)
                 PropertyChanged(this, new 
PropertyChangedEventArgs(propertyName));
         }
         private List<string> _cellValues;
         public List<string> CellValues
         {
             get { return _cellValues; }
             set
             {
                 _cellValues = value;
                 OnPropertyChanged("CellValues");
             }
         }
     }
}
L'ultimo dettaglio che manca è come apro da un menù la popup:
[...]
                     BackendEmulatorViewModel bevm = new 
BackendEmulatorViewModel(new List<string>() { "send1","send2" }, new 
List<string>() { "receive1", "receive2" }); //per i test, da sostituire 
con valori attuali
                     //Posiziono il popup sotto il treeview item 
relativo all'azione attuale (usa un'extension method)
                     revm.BackendEmulationEditor.DataContext = bevm;
                     Popup backendEmulationEditorWindow = 
revm.BackendEmulationEditor.EmulatorPopup;
                     var item = ((revm.View as 
IResponseEditorView).TreeViewEditorView.tvParameters).ContainerFromItem(this) 
as TreeViewItem;
                     backendEmulationEditorWindow.PlacementTarget = item;
                     backendEmulationEditorWindow.Placement = 
PlacementMode.Bottom;
                     backendEmulationEditorWindow.IsOpen = true; //qui 
si apre la popup
                     backendEmulationEditorWindow.StaysOpen = true;
Per chiarire cosa funziona e cosa no:
mettendo un break sulle righe return di questo converter:
     public class EmptyConverter : IValueConverter
     {
         public object Convert(object value, Type targetType, object 
parameter, CultureInfo culture)
         {
             return value;
         }
         public object ConvertBack(object value, Type targetType, object 
parameter, CultureInfo culture)
         {
             return value;
         }
     }
e sulle get e set della property SelectedVal, il debugger si ferma sulla 
get di SelectedVal in apertura ma non sul return di Convert, poi quando 
scelgo un valore nelle combobox (correttamente riempite) e premo enter, 
il debugger si ferma sul ConvertBack ma non sul set di SelectedVal.
Cioè, il binding funziona? Si e no! Non capisco davvero, e tutto ciò è 
frustrante, non riesco a trovare qualcuno che in rete abbia avuto un 
problema simile, forse cerco male o forse non ho capito un tubazzo...
ciao

Aggiornamento:
il problema non sta nella DataGridComboBoxColumn, il binding non va a 
buon fine neanche con una DataGridTextColumn:
//DataGridComboBoxColumn dgcbc = new DataGridComboBoxColumn()
DataGridTextColumn dgcbc = new DataGridTextColumn()
{
	Header = ec.Header,
	//ItemsSource = ec.AvailableValues,
	//SelectedItemBinding = binding,
	//SelectedValuePath = ec.SelectedVal,
	//SelectedValueBinding = binding,
	//DisplayMemberPath = ec.SelectedValue
	Binding = binding
};
comincio a pensare che il modo con cui apro la Popup window non sia 
ortodosso... in pratica il componente che contiene la Popup (che 
contiene la DataGrid) è istanziato a priori, contenuto nel componente 
padre, e viene aperto alla bisogna.
Quello che faccio prima di aprire è creare un viewmodel apposito e 
cambiare al popup il datacontext sotto al ... sedere.
ripeto:
                     BackendEmulatorViewModel bevm = new 
BackendEmulatorViewModel(new List<string>() { "send1","send2" }, new 
List<string>() { "receive1", "receive2" }); //per i test, da sostituire 
con valori attuali
                     //Posiziono il popup sotto il treeview item 
relativo all'azione attuale (usa un'extension method)
                     revm.BackendEmulationEditor.DataContext = bevm;
                     Popup backendEmulationEditorWindow = 
revm.BackendEmulationEditor.EmulatorPopup;
                     var item = ((revm.View as 
IResponseEditorView).TreeViewEditorView.tvParameters).ContainerFromItem(this) 
as TreeViewItem;
                     backendEmulationEditorWindow.PlacementTarget = item;
                     backendEmulationEditorWindow.Placement = 
PlacementMode.Bottom;
                     backendEmulationEditorWindow.IsOpen = true; //qui 
si apre la popup
continuo a indagare e vi tengo agiornati, scusate il "rumore di fondo" 
ma può darsi che tutto ciò serva a qualcun altro, dunque vado avanti

 aperto alla bisogna.
ma... il DataContext è definto a livello di?
Perchè il controllo Popup ha un po' di caratteristiche speciali nella 
gestione del DataBinding perchè a tutti gli effetti quello che finisce 
dentro il popup è in una *window* *diversa* da quella dove sei tu.


 dentro il popup è in una *window* *diversa* da quella dove sei tu.
ho visto che in effetti c'era un problema di datacontext, perché 
succedeva una cosa strana facendo così (BackendEmulationEditor è il mio 
componente popup con datagrid inclusa):
revm.BackendEmulationEditor = new UI.BackendEmulationEditor();
revm.BackendEmulationEditor.DataContext = bevm; //bevm è il viewmodel
in pratica pensavo che questo datacontext fosse ereditato da Popup e 
Datagrid, invece datagrid ereditava il datacontext del componente da cui 
chiamavo popup.IsOpen = true (ma perché !?!?)
dunque, dopo aver forzato il datacontext così:
revm.BackendEmulationEditor.BackendEmulatorDatagrid.DataContext = bevm;
revm.BackendEmulationEditor.EmulatorPopup.DataContext = bevm;
pensavo di aver risolto, invece no, perché dopo aver sistemato un 
problema sul db di una solution di esempio che avevo scaricato, ho visto 
che se si imposta un ItemsSource sul datagrid, i binding dichiarati 
nelle colonne hanno come datacontext la classe che rappresenta le righe:
Binding binding = new Binding("SelectedVal");
binding.Converter = new EmptyConverter();
DataGridTextColumn dgcbc = new DataGridTextColumn()
{
	Header = ec.Header,
	Binding = binding
};
BackendEmulatorDatagrid.Columns.Add(dgcbc);
SelectedVal nel mio caso deve stare su EmulationRow...
Dunque in conclusione ho capito che:
1) invece di fare tentativi a vanvera dovevo studiare di più il 
componente _prima_ di usarlo
2) la datagrid è pensata per strutture con colonne note a priori, perché 
la classe che rappresenta la riga deve sapere a quali campi riferire le 
properties a cui bindare i valori delle celle
3) forse la datagrid non va bene nel mio caso in cui avrei bisogno di 
una matrice meno "db" oriented, oppure devo lanciarmi in "magheggi" poco 
ortodossi (poco ortodossi è un modo per dire che io non sono capace di 
capirli e gestirli...)
4) forse ho sbagliato mestiere (ho questi momenti di frustrazione 
softwaristica, abbiate pazienza ;)