mercoledì 21 settembre 2011

WPF - DataGrid (DeferRefresh exception)

C# 4.0
In una view con all’interno una DataGrid mi capitava a volte, aggiungendo o modificando una riga, la seguente exception:
"’DeferRefresh’ is not allowed during an AddNew or EditItem transaction"

Girando quindi per la rete ho trovato un behaviour che ho poi adattato alle mie esigenze, in pratica al lost focus della grid viene forzato un CommitNew o un CommitEdit a seconda del caso.

A seguire il behaviour e la relativa modifica allo XAML.

using System.ComponentModel;
using System.Windows.Input;
using System.Windows;
using System.Windows.Controls;

namespace Admin.Console.Behaviour
{
    class DataGridCommitOnUnfocusedBehaviour
    {
        #region DataGridCommitOnUnfocusedBehaviour

        public static bool GetDataGridCommitOnUnfocused(DataGrid datagrid)
        {
            return (bool)datagrid.GetValue(DataGridCommitOnUnfocusedProperty);
        }

        public static void SetDataGridCommitOnUnfocused(
         DataGrid datagrid, bool value)
        {
            datagrid.SetValue(DataGridCommitOnUnfocusedProperty, value);
        }

        public static readonly DependencyProperty DataGridCommitOnUnfocusedProperty =
            DependencyProperty.RegisterAttached(
            "DataGridCommitOnUnfocused",
            typeof(bool),
            typeof(DataGridCommitOnUnfocusedBehaviour),
            new UIPropertyMetadata(false, OnDataGridCommitOnUnfocusedChanged));

        static void OnDataGridCommitOnUnfocusedChanged(
         DependencyObject depObj, DependencyPropertyChangedEventArgs e)
        {
            DataGrid datagrid = depObj as DataGrid;
            if (datagrid == null)
                return;

            if (e.NewValue is bool == false)
                return;

            if ((bool)e.NewValue)
            {
                datagrid.LostKeyboardFocus += CommitDataGridOnLostFocus;
                datagrid.DataContextChanged += CommitDataGridOnDataContextChanged;
            }
            else
            {
                datagrid.LostKeyboardFocus -= CommitDataGridOnLostFocus;
                datagrid.DataContextChanged -= CommitDataGridOnDataContextChanged;
            }
        }

        static void CommitDataGridOnLostFocus(object sender, KeyboardFocusChangedEventArgs e)
        {
            DataGrid senderDatagrid = sender as DataGrid;

            if (senderDatagrid == null)
                return;

            UIElement focusedElement = Keyboard.FocusedElement as UIElement;
            if (focusedElement == null)
                return;

            DataGrid focusedDatagrid = GetParentDatagrid(focusedElement); //let's see if the new focused element is inside a datagrid

            if (focusedDatagrid == senderDatagrid)
            {
                return;
                //if the new focused element is inside the same datagrid, then we don't need to do anything;
                //this happens, for instance, when we enter in edit-mode: the DataGrid element loses keyboard-focus, which passes to the selected DataGridCell child
            }

            //otherwise, the focus went outside the datagrid; in order to avoid exceptions like ("DeferRefresh' is not allowed during an AddNew or EditItem transaction")
            //or ("CommitNew is not allowed for this view"), we undo the possible pending changes, if any

            IEditableCollectionView collection = senderDatagrid.Items;
            if (collection.IsEditingItem)
            {
                collection.CommitEdit();
            }
            else if (collection.IsAddingNew)
            {
                collection.CommitNew();
            }
        }

        private static DataGrid GetParentDatagrid(UIElement element)
        {
            UIElement childElement; //element from which to start the tree navigation, looking for a Datagrid parent

            if (element is ComboBoxItem) //since ComboBoxItem.Parent is null, we must pass through ItemsPresenter in order to get the parent ComboBox
            {
                ItemsPresenter parentItemsPresenter = VisualTreeFinder.FindParentControl<ItemsPresenter>((element as ComboBoxItem));
                ComboBox combobox = parentItemsPresenter.TemplatedParent as ComboBox;
                childElement = combobox;
            }
            else
            {
                childElement = element;
            }

            DataGrid parentDatagrid = VisualTreeFinder.FindParentControl<DataGrid>(childElement); //let's see if the new focused element is inside a datagrid
            return parentDatagrid;
        }

        static void CommitDataGridOnDataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
        {
            DataGrid senderDatagrid = sender as DataGrid;

            if (senderDatagrid == null)
                return;

            IEditableCollectionView collection = senderDatagrid.Items;

            if (collection.IsEditingItem)
            {
                collection.CommitEdit();
            }
            else if (collection.IsAddingNew)
            {
                collection.CommitNew();
            }
        }

        #endregion DataGridCommitOnUnfocusedBehaviour
    }
}

class VisualTreeFinder
    {
         /// <summary>
        /// Find a specific parent object type in the visual tree
        /// </summary>
        public static T FindParentControl<T>(DependencyObject outerDepObj) where T : DependencyObject
        {
            DependencyObject dObj = VisualTreeHelper.GetParent(outerDepObj);
            if (dObj == null)
                return null;

            if (dObj is T)
                return dObj as T;

            while ((dObj = VisualTreeHelper.GetParent(dObj)) != null)
            {
                if (dObj is T)
                    return dObj as T;
            }

            return null;
        }
    }

Nello XAML ho introdotto il seguente attributo evidenziato in giallo:

<DataGrid AutoGenerateColumns="False" Height="200" HorizontalAlignment="Left" Margin="132,43,0,0" VerticalAlignment="Top" Width="400"
                                            ItemsSource="{Binding Threshold.Hierarchies, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
behaviour:DataGridCommitOnUnfocusedBehaviour.DataGridCommitOnUnfocused="True">
                                            <DataGrid.RowValidationRules>
                                                <Common:RowDataInfoValidationRule ValidationStep="UpdatedValue" />
                                            </DataGrid.RowValidationRules>
                                            <DataGrid.Columns>
                                                <DataGridTextColumn Width="190" Binding="{Binding Path=Code, UpdateSourceTrigger=PropertyChanged}"  EditingElementStyle="{StaticResource CellEditStyle}"
                                                 Header="{Binding Source={StaticResource CustomResources}, Path=LocalizedStrings.grdThresholdHiearchyCode}"/>
                                                <DataGridCheckBoxColumn Width="190" Binding="{Binding Path=Included, UpdateSourceTrigger=PropertyChanged}"
                                                 Header="{Binding Source={StaticResource CustomResources}, Path=LocalizedStrings.grdThresholdHiearchyIncluded}"/>
                                            </DataGrid.Columns>
                                        </DataGrid>




mercoledì 24 agosto 2011

Silverlight - Tooltip su dei giorni particolari di un controllo Calendar

Silverlight 4.0

L’esigenza è quella ottenere un tooltip su alcuni giorni particolari, evidenziati in rosso, di un oggetto Calendar, ad esempio per evidenziare le proprie scadenze fiscali; e questa è la soluzione che ho adottato.
Come prima cosa ho creato i seguenti converter, uno per i giorni in “rosso” e l’altro per il tooltip:

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Windows.Data;
using System.Windows.Media;

namespace ContabilitaSelfService.Converter
{
    public class BackgroundDayCalendarConverter : IValueConverter
    {
        //public static IList<DateTime> Holidays { get; set; }
        public static IList<DateTime> Evidences { get; set; }

        public object Convert(object value,
                            Type targetType,
                            object parameter,
                            CultureInfo culture)
        {
            DateTime date = (DateTime)value;

            if (Evidences != null && Evidences.Contains(date))
                return new SolidColorBrush(Colors.Red);
            else if (date.Date == DateTime.Now.Date)
                return new SolidColorBrush(Colors.LightGray);
            else
                return new SolidColorBrush(Colors.Transparent);
        }

        public object ConvertBack(object value, Type targetType,
        object parameter, CultureInfo culture)
        {
            return null;
        }
    }
}


using System;
using System.Collections.Generic;
using System.Globalization;
using System.Windows.Data;

namespace ContabilitaSelfService.Converter
{
    public class TooltipDayCalendarConverter : IValueConverter
    {
        public static IDictionary<DateTime, string> Tooltips { get; set; }

        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            DateTime date = (DateTime)value;

            if (Tooltips != null && Tooltips.ContainsKey(date))
                return Tooltips[date];

            return null;
        }


        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return null;
        }
    }
}

Ho creato poi in un’ottica di riusabilità il seguente user control
<UserControl x:Class="ContabilitaSelfService.UserControls.DeskTop"

    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"

    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"

    xmlns:prim="clr-namespace:System.Windows.Controls.Primitives;assembly=System.Windows.Controls"

    xmlns:src="clr-namespace:ContabilitaSelfService.Converter" xmlns:sdk="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls" mc:Ignorable="d"

    d:DesignHeight="500" d:DesignWidth="180">

    <UserControl.Resources>

        <src:BackgroundDayCalendarConverter x:Key="BackgroundDayCalendarConverter" />

        <src:TooltipDayCalendarConverter x:Key="TooltipDayCalendarConverter" />

        <Style x:Key="CalendarDayButtonStyle1" TargetType="prim:CalendarDayButton">

            <Setter Property="Template">

                <Setter.Value>

                    <ControlTemplate TargetType="prim:CalendarDayButton">

                        <Grid Background="{Binding Converter={StaticResource BackgroundDayCalendarConverter}, Path=Date}"

                              ToolTipService.ToolTip="{Binding Converter={StaticResource TooltipDayCalendarConverter}, Path=Date}">

                            <ContentControl x:Name="Content" Margin="5,1,5,1" Content="{TemplateBinding Content}" />

                        </Grid>

                    </ControlTemplate>

                </Setter.Value>

            </Setter>

        </Style>

    </UserControl.Resources>

    <Grid x:Name="LayoutRoot" Background="White">
        <sdk:Calendar HorizontalAlignment="Right" CalendarDayButtonStyle="{StaticResource CalendarDayButtonStyle1}" >
        </sdk:Calendar>
    </Grid>
</UserControl>

Da notare la parte di binding relativa ai converter.
A questo punto ho caricato le collection interne Evidences e Tooltips prendendo le informazioni da una tabella, nella quale sono state memorizzate tutte le mie scadenze. Tralasciando la parte di recupero dei dati, questa è la procedura per il caricamento delle scadenze:

private void LoadScadenzeCallBack(LoadOperation<Scadenzario> obj)
        {
            BackgroundDayCalendarConverter.Evidences = new List<DateTime>();
            TooltipDayCalendarConverter.Tooltips = new Dictionary<DateTime, string>();

            foreach (var scadenza in _context.Scadenzarios)
            {
                BackgroundDayCalendarConverter.Evidences.Add(scadenza.DataScadenza);
                TooltipDayCalendarConverter.Tooltips.Add(scadenza.DataScadenza, scadenza.Descrizione);  
            }           
        }

Nel mio caso, questo è il risultato finale:







martedì 23 agosto 2011

Silverlight 4.0 - WCF RIA Services - Enterprise Logging Application Block

SO Windows 7 a 64 bit - IIS 7.5 - VS2010 service pack1

Avevo questo tipo di problematica:

"Nella mia Silverlight Business Application ho implementato il Logging Application Block con l’estensione per Silverlight come da documentazione e tutto funziona correttamente, riesco a scrivere nel mio file di log tutte le informazioni che voglio, ma il fatto è che funziona solo se eseguo l’applicazione in emulazione, cioè con l’option “Usa Visual Studio Development Server”, se invece eseguo sempre da VS con “Usa server Web IIS Locale” l’applicazione non mi logga più niente. Credo sia un problema di permessi, però io ho dato i permessi alla cartella a tutto il mondo; ho cambiato anche l’identità di pool, impostandola a Nework Service, perché ho letto in un blog che WCF gira con Network Service, ma … niente da fare! E non so più dove sbattere la testa ... Naturalmente nell'event log di Windows non c'è nessun errore. A questo punto, qualsiasi altro suggerimento è bene accetto! Grazie mille!"

Alla fine ho trovato il motivo di tale anomalia.

Grazie Fiddler2!
Come capita spesso in questi casi, era una "baggianata"; in pratica era sbagliato l'endpoint del servizio WCF dell'applicazione Silverlight.
Era impostato nel seguente modo:

<endpoint address="/LoggingService.svc" ...

deve essere invece

<endpoint address="/MiaApplicazione.Web/LoggingService.svc" ...

Che sudata però ;-)

venerdì 19 agosto 2011

Silverlight - Close a ChildWindow

C# 4.0

Per chiudere una Child Window era stato impostato il seguente codice nell’evento di chiusura della view

private void WindowChild1_Close (object sender, MouseButtonEventArgs e)
        {
            DialogResult = true;
            Close();
        }
 
Questo però comportava che dopo un paio di chiusure della suddetta child l’intera applicazione era come fosse “disabilitata”, non si riusciva più ad interagire con il browser.

Ad oggi quindi per risolvere tale anomalia è stata aggiunta la seguente riga di codice:
 
Application.Current.RootVisual.SetValue(IsEnabledProperty, true);

L’evento è stato conseguentemente modificato nel seguente modo:

private void WindowChild1_Close (object sender, MouseButtonEventArgs e)
        {
            // Workaround to correctly close a child window
            Application.Current.RootVisual.SetValue(IsEnabledProperty, true);

            DialogResult = true;
            Close();
        }

… e così funziona!

martedì 9 agosto 2011

Silverlight 4.0 & IIS 7.5

Silverlight 4.0 (C#) & IIS 7.5

Pubblicando una Silverlight Business Application su IIS e richiamando la pagina di default dell’applicazione stessa http://localhost/BusinessApplication.Web/#/Home si ottiene il seguente messaggio d’errore: "Operazione Load non riuscita per la query 'GetUser'. Errore del server remoto: NotFound."



Il suddetto errore è stato risolto disabilitando l’autenticazione Windows su IIS.









martedì 2 agosto 2011

Web Services & SSL

C# 2.0 & IIS 7.5

Si vuole richiamare da un’applicazione client un metodo di un servizio web in modalità SSL (Secure Socket Layer) ed inoltre si vogliono nascondere i parametri del suddetto web method.
Si dà per assunto l’installazione del certificato nel server.

1.       Richiamare un metodo web in modalità SSL da un’applicazione client :

// Viene istanziato l’oggetto che si riferisce al web service
            DocumentHistory d = new DocumentHistory();
 
NetworkCredential networkCredential = new NetworkCredential("ciccio", "pasticcio");
       d.Credentials = networkCredential;

       ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };

       // Viene invocato il metodo richiesto
            object[] r = d.DepartmentReport("??", "??”, “!!”);

Da tenere presente quindi che se viene omessa la riga di codice evidenziata in giallo si verifica la seguente web exception:
 
“Connessione sottostante chiusa: Impossibile stabilire una relazione di trust per il canale sicuro SSL/TLS..”


2.  Per nascondere i parametri del metodo web si deve aggiungere il seguente elemento nel web.config del servizio web, all’interno dell’elemento <system.web>:

<system.web>
               <webServices>
              <protocols>
                <remove name="HttpPostLocalhost"/>
              </protocols>
            </webServices>
            </system.web>