none
Data Binding e controllo utente RRS feed

  • Domanda

  • Salve a tutti,
    in questi giorni sto facendo delle prove di utilizzo di WPF e mi sto trovando in difficoltà con il binding di un controllo utente personalizzato.
    Lo scopo dell'esempio che sto sviluppando è quello di visualizzare all'interno di una DataGrid una lista di elementi.

    La lista è una ObservableCollection di tipo Caratteristica: ovvero una classe creata ad hoc che contiene nome, descrizione e valore della caratteristica. La lista è inizializzata "a mano" all'interno del costruttore della MainWindow.

    Il codice dello XAML è il seguente:

    <window style="color:#5b5b5b;font-family:'Segoe UI', Tahoma, Verdana, Arial, Helvetica, sans-serif;font-size:14px;line-height:19.6000003814697px;" x:class="wpfGrigliaEventi.MainWindow"><window x:class="wpfGrigliaEventi.MainWindow"></window></window>

    <Window x:Class="wpfGrigliaEventi.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow" Height="350" Width="525"
    xmlns:local="clr-namespace:wpfGrigliaEventi">
    <Grid>
    <DataGrid x:Name="datagrid1" ItemsSource="{Binding}" AutoGenerateColumns="False">
      <DataGrid.Columns>
          <DataGridTextColumn x:Name="Nome" Header="Nome" Binding="{Binding Nome}" Width="60" />
          <DataGridTextColumn x:Name="Descrizione"  Header="Descrizione" Binding="{Binding Descrizione}" Width="160" />
          <DataGridTextColumn x:Name="Valore" Header="Valore" Binding="{Binding Value}" Width="50"/>
                    <DataGridTemplateColumn Header="LI/LS">
                        <DataGridTemplateColumn.CellTemplate>
                            <DataTemplate>
                                <local:Barra LunghezzaBarra="{Binding Value}" Width="100" />
                            </DataTemplate>
                        </DataGridTemplateColumn.CellTemplate>
                    </DataGridTemplateColumn>
                </DataGrid.Columns>
            </DataGrid>
        </Grid>
    </Window>

    Ci sono 3 colonne che contengono dei DataGridTextColum e che fanno il bind con gli attributi della classe Caratteristica e una colonna che contiene un controllo utente fatto da me che disegna un rettangolo che ha una lunghezza che si può passare tramite una DependecyProperty (LunghezzaBarra).

    Ecco il codice della classe Barra

    public partial class Barra : UserControl
    {
            public Barra()
            {
                InitializeComponent();
            }
    
            public static readonly DependencyProperty valoreBarraProperty = DependencyProperty.Register("LunghezzaBarra", typeof(int), typeof(Barra));
            public int LunghezzaBarra
            {
                get { return Convert.ToInt32(GetValue(valoreBarraProperty)); }
                set { 
                        SetValue(valoreBarraProperty, value);
                }
            }
    
    
            private void CreateARectangle()
            {
                Rectangle rectangle = new Rectangle();
                rectangle.Width = LunghezzaBarra;
    
                SolidColorBrush greenBrush = new SolidColorBrush();
                greenBrush.Color = Colors.Green;
    
                rectangle.Fill = greenBrush;
    
                LayoutRoot.Children.Add(rectangle);
            }
    
            protected override void OnRender(DrawingContext drawingContext)
            {
                CreateARectangle();
                base.OnRender(drawingContext);
            }
        }

    Se nello XAML della pagina principale forzo il valore di LunghezzaBarra ad un numero qualsiasi la barra viene visualizzata nella quarta colonna che invece inserisco il bind con il valore Value di Caratteristica l'applicazione parte, ma non viene visualizzato nulla.

    Chiedo cortesemente un aiuto per trovare il problema.


    Grazie in anticipo e buona giornata a tutti

    Andrea
    martedì 12 maggio 2015 09:50

Risposte

  • Grazie mille,

    ho provato ad implementare una ListBox e la barra viene disegnata quando viene chiamato il metodo OnRender.

    Ho provato a fare un passo successivo per verificare la questione dell'aggiornamento della proprietà Value di Caratteristica.

    Ho creato un pulsante che cambia il valore dell'attributo Value al primo elemento della lista.

    private void btn_click(object sender, RoutedEventArgs e)
    {
        lista[0].Value = 89;
    }

    Alla pressione del pulsante però la barra resta "ferma" sul valore di prima.

    Resta ferma perchè non hai detto alla tua proprietà di chiamare un callback quando il suo valore cambia. Se avessi questo callback potresti ridimensionare il rettangolo con il valore corrente.

    EDIT

    Quando registri la dependecy property puoi usare il quarto parametro del metodo "register" per specificare questo callback.

    • Modificato BlueLed mercoledì 13 maggio 2015 09:50
    • Contrassegnato come risposta Andrea Marteletti mercoledì 13 maggio 2015 12:14
    mercoledì 13 maggio 2015 09:45

Tutte le risposte

  • Salve a tutti,
    in questi giorni sto facendo delle prove di utilizzo di WPF e mi sto trovando in difficoltà con il binding di un controllo utente personalizzato.
    Lo scopo dell'esempio che sto sviluppando è quello di visualizzare all'interno di una DataGrid una lista di elementi.

    La lista è una ObservableCollection di tipo Caratteristica: ovvero una classe creata ad hoc che contiene nome, descrizione e valore della caratteristica. La lista è inizializzata "a mano" all'interno del costruttore della MainWindow.

    Il codice dello XAML è il seguente:

    <window style="color:#5b5b5b;font-family:'Segoe UI', Tahoma, Verdana, Arial, Helvetica, sans-serif;font-size:14px;line-height:19.6000003814697px;" x:class="wpfGrigliaEventi.MainWindow"><window x:class="wpfGrigliaEventi.MainWindow"></window></window>

    <Window x:Class="wpfGrigliaEventi.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow" Height="350" Width="525"
    xmlns:local="clr-namespace:wpfGrigliaEventi">
    <Grid>
    <DataGrid x:Name="datagrid1" ItemsSource="{Binding}" AutoGenerateColumns="False">
      <DataGrid.Columns>
          <DataGridTextColumn x:Name="Nome" Header="Nome" Binding="{Binding Nome}" Width="60" />
          <DataGridTextColumn x:Name="Descrizione"  Header="Descrizione" Binding="{Binding Descrizione}" Width="160" />
          <DataGridTextColumn x:Name="Valore" Header="Valore" Binding="{Binding Value}" Width="50"/>
                    <DataGridTemplateColumn Header="LI/LS">
                        <DataGridTemplateColumn.CellTemplate>
                            <DataTemplate>
                                <local:Barra LunghezzaBarra="{Binding Value}" Width="100" />
                            </DataTemplate>
                        </DataGridTemplateColumn.CellTemplate>
                    </DataGridTemplateColumn>
                </DataGrid.Columns>
            </DataGrid>
        </Grid>
    </Window>

    Ci sono 3 colonne che contengono dei DataGridTextColum e che fanno il bind con gli attributi della classe Caratteristica e una colonna che contiene un controllo utente fatto da me che disegna un rettangolo che ha una lunghezza che si può passare tramite una DependecyProperty (LunghezzaBarra).

    Ecco il codice della classe Barra

    public partial class Barra : UserControl
    {
            public Barra()
            {
                InitializeComponent();
            }
    
            public static readonly DependencyProperty valoreBarraProperty = DependencyProperty.Register("LunghezzaBarra", typeof(int), typeof(Barra));
            public int LunghezzaBarra
            {
                get { return Convert.ToInt32(GetValue(valoreBarraProperty)); }
                set { 
                        SetValue(valoreBarraProperty, value);
                }
            }
    
    
            private void CreateARectangle()
            {
                Rectangle rectangle = new Rectangle();
                rectangle.Width = LunghezzaBarra;
    
                SolidColorBrush greenBrush = new SolidColorBrush();
                greenBrush.Color = Colors.Green;
    
                rectangle.Fill = greenBrush;
    
                LayoutRoot.Children.Add(rectangle);
            }
    
            protected override void OnRender(DrawingContext drawingContext)
            {
                CreateARectangle();
                base.OnRender(drawingContext);
            }
        }

    Se nello XAML della pagina principale forzo il valore di LunghezzaBarra ad un numero qualsiasi la barra viene visualizzata nella quarta colonna che invece inserisco il bind con il valore Value di Caratteristica l'applicazione parte, ma non viene visualizzato nulla.

    Chiedo cortesemente un aiuto per trovare il problema.


    Grazie in anticipo e buona giornata a tutti

    Andrea

    Qual è il tuo DataContext? Come e dove l'hai impostato?

    A parte il controllo personalizzato, per quanto riguarda il resto, il binding funziona? Al controllo personalizzato probabilmente manca il DataContext che gli dovrebbe fornire la proprietà "Value".

    Tra l'altro puoi disegnare questa barra anche usando solo un oggetto Rectangle e fare il binding della lunghezza alla sua propertà "Width".

    mercoledì 13 maggio 2015 06:29
  • Ciao,
    grazie per avermi risposto.

    Non avevo pensato a disegnare direttamente la barra, tuttavia questo è un esempio di partenza per uno user control più complesso che forse dovrò sviluppare più avanti.

    Per quanto riguarda il DataContext non l'ho specificato per il controllo utente, perché se non sbaglio lo eredita da quello del controllo DataGrid; e per il DataGrid l'ho specificato a livello di codice

    public MainWindow()
    {
         InitializeComponent();
    
         Random rand = new Random();
         for (int idx = 0; idx < 10; idx++ )
         {
              lista.Add(new Caratteristica("Car"+idx, "Caratteristica "+idx, rand.Next(0, 70)));
         }
         
         datagrid1.ItemsSource = lista;
          
    }


    mercoledì 13 maggio 2015 07:00
  • Ciao,
    grazie per avermi risposto.

    Non avevo pensato a disegnare direttamente la barra, tuttavia questo è un esempio di partenza per uno user control più complesso che forse dovrò sviluppare più avanti.

    Per quanto riguarda il DataContext non l'ho specificato per il controllo utente, perché se non sbaglio lo eredita da quello del controllo DataGrid; e per il DataGrid l'ho specificato a livello di codice

    public MainWindow()
    {
         InitializeComponent();
    
         Random rand = new Random();
         for (int idx = 0; idx < 10; idx++ )
         {
              lista.Add(new Caratteristica("Car"+idx, "Caratteristica "+idx, rand.Next(0, 70)));
         }
         
         datagrid1.ItemsSource = lista;
          
    }


    Non hai impostato il DataContext:

    DataContext = lista;

    L'ItemSource lo hai già impostato da XAML con:

    ItemSource={Binding}

    Un altro problema è che ogni volta che vai disegnare il rettangolo ne crei uno nuovo!!!!!

    mercoledì 13 maggio 2015 08:06
  • Togliendo 

    datagrid1.ItemsSource = lista; 

    e inserendo 

    DataContext = lista;

    la barra continua a non venire disegnata.

    Ho provato a seguire un'altra strada e invece che disegnare la barra quando viene chiamato il metodo OnRender, ho creato un metodo che viene chiamato in fase di caricamento del controllo utente

    <UserControl x:Class="wpfGrigliaEventi.Barra"
                 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
                 xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
                 mc:Ignorable="d" Loaded="UserControl_Loaded">
        <Grid Name="LayoutRoot"  HorizontalAlignment="Left">
        </Grid>
    </UserControl>

    Metodo UserControl_Loaded

    private void UserControl_Loaded(object sender, RoutedEventArgs e)
    {
         CreateARectangle();
    }

    Facendo così la barra viene disegnata.

    mercoledì 13 maggio 2015 08:35
  • Togliendo 

    datagrid1.ItemsSource = lista; 

    e inserendo 

    DataContext = lista;

    la barra continua a non venire disegnata.

    Ho provato a seguire un'altra strada e invece che disegnare la barra quando viene chiamato il metodo OnRender, ho creato un metodo che viene chiamato in fase di caricamento del controllo utente

    <UserControl x:Class="wpfGrigliaEventi.Barra"
                 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
                 xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
                 mc:Ignorable="d" Loaded="UserControl_Loaded">
        <Grid Name="LayoutRoot"  HorizontalAlignment="Left">
        </Grid>
    </UserControl>

    Metodo UserControl_Loaded

    private void UserControl_Loaded(object sender, RoutedEventArgs e)
    {
         CreateARectangle();
    }

    Facendo così la barra viene disegnata.

    In questo modo, però, quando aggiorni la proprietà Value di Caratteristica, il rettangolo non cambia lunghezza.

    Dato che non ho il DataGrid, usando una ListBox e ListBox.ItemTemplate, ho provato come ti ho detto prima ed il rettangolo si vede.


    • Modificato BlueLed mercoledì 13 maggio 2015 08:44
    mercoledì 13 maggio 2015 08:44
  • Grazie mille,

    ho provato ad implementare una ListBox e la barra viene disegnata quando viene chiamato il metodo OnRender.

    Ho provato a fare un passo successivo per verificare la questione dell'aggiornamento della proprietà Value di Caratteristica.

    Ho creato un pulsante che cambia il valore dell'attributo Value al primo elemento della lista.

    private void btn_click(object sender, RoutedEventArgs e)
    {
        lista[0].Value = 89;
    }

    Alla pressione del pulsante però la barra resta "ferma" sul valore di prima.

    mercoledì 13 maggio 2015 09:25
  • Grazie mille,

    ho provato ad implementare una ListBox e la barra viene disegnata quando viene chiamato il metodo OnRender.

    Ho provato a fare un passo successivo per verificare la questione dell'aggiornamento della proprietà Value di Caratteristica.

    Ho creato un pulsante che cambia il valore dell'attributo Value al primo elemento della lista.

    private void btn_click(object sender, RoutedEventArgs e)
    {
        lista[0].Value = 89;
    }

    Alla pressione del pulsante però la barra resta "ferma" sul valore di prima.

    Resta ferma perchè non hai detto alla tua proprietà di chiamare un callback quando il suo valore cambia. Se avessi questo callback potresti ridimensionare il rettangolo con il valore corrente.

    EDIT

    Quando registri la dependecy property puoi usare il quarto parametro del metodo "register" per specificare questo callback.

    • Modificato BlueLed mercoledì 13 maggio 2015 09:50
    • Contrassegnato come risposta Andrea Marteletti mercoledì 13 maggio 2015 12:14
    mercoledì 13 maggio 2015 09:45
  • Grazie per l'aiuto, ora funziona tutto.

    In realtà avevo anche dimenticato di Implementare INotifyPropertyChanged sulla classe Caratteristica.

    mercoledì 13 maggio 2015 11:53