0

I'm working on a project that uses the Silicon Labs SDK. After making a wrapper for their SDK I made a WPF project to visualize some of the results from the SDK. My first test was with the GetNumDevices function. My WPF knowledge is not very strong so sorry about anything that makes your eyes bleed, but for a demonstration let me show you what I have. First for the API method I am talking about.

[DllImport("CP210xManufacturing.dll", CallingConvention = CallingConvention.StdCall)]
public static extern CP210x_STATUS CP210x_GetNumDevices(ref int lpdwNumDevices);

instead of making 2 labels and 1 button to visualize this method I decided to make a user control.

CP210UserControl.xaml

<UserControl x:Class="SiliconLabsRosettaStone.CP210UserControl"
             x:Name="panel"
             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" 
             d:DesignHeight="50" d:DesignWidth="300"
             DataContext="{Binding ElementName=panel}">
    <Border BorderBrush="DarkGreen" BorderThickness="3" Margin="2">
        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto"/>
                <ColumnDefinition />
                <ColumnDefinition Width="75"/>
            </Grid.ColumnDefinitions>

            <TextBlock Text="{Binding Caption}" Margin="3,0" />
            <Border Grid.Column="1" BorderBrush="Black" BorderThickness="2" Margin="3,0">
                <TextBlock x:Name="responseTextBlock" Text="{Binding Response}" />
            </Border>
            <Button Grid.Column="2" Content="Execute" Click="Button_Click" Margin="3,0" />
        </Grid>
    </Border>
</UserControl>

CP210UserControl.xaml.cs

public partial class CP210UserControl : UserControl
{
    public CP210UserControl()
    {
        InitializeComponent();
    }
    public void SetupUserControl(Func<CP210UserControl, CP210x_STATUS> function)
    {
        this.cp210Function = function;
    }
    private void Button_Click(object sender, RoutedEventArgs e)
    {
        CP210x_STATUS response = cp210Function(this);
        this.responseTextBlock.Background = response == CP210x_STATUS.CP210x_SUCCESS ? null : new SolidColorBrush(Colors.Pink);
    }

    public string Caption
    {
        get { return (string)GetValue(CaptionProperty); }
        set { SetValue(CaptionProperty, value); }
    }

    public string Response
    {
        get { return (string)GetValue(ResponseProperty); }
        set { SetValue(ResponseProperty, value); }
    }

    public static readonly DependencyProperty CaptionProperty = DependencyProperty.Register("Caption", typeof(string), typeof(CP210UserControl), new PropertyMetadata("CP210x"));
    public static readonly DependencyProperty ResponseProperty = DependencyProperty.Register("Response", typeof(string), typeof(CP210UserControl), new PropertyMetadata(""));

    private Func<CP210UserControl, CP210x_STATUS> cp210Function;
}

That is the file that I have a question about because of its usage.

this is how I use it in my main window

MainWindow.xaml

<StackPanel>
    <local:CP210UserControl x:Name="cpGetNumDevices" Grid.Row="1" Grid.ColumnSpan="3" Caption="CP210x_GetNumDevices" />
</StackPanel>

Now for the part I hate. How i hook it up :/

MainWindow.xaml.cs

    private void Window_Loaded(object sender, RoutedEventArgs e)
    {
        cpGetNumDevices.SetupUserControl(getNumberDevices);
    }
    private CP210x_STATUS getNumberDevices(CP210UserControl control)
    {
        int devices = 0;
        var result = CP210xWrapper.CP210x_GetNumDevices(ref devices);
        control.Response = string.Format("Number Of Devices:{0}", devices);
        return result;
    }

I would think that I could make the xaml look like this

    <local:CP210UserControl x:Name="cpGetNumDevices" Grid.Row="1" Grid.ColumnSpan="3" Caption="CP210x_GetNumDevices" cp210Function="getNumberDevices" />

if i change my user control to this.

    public Func<CP210UserControl, CP210x_STATUS> cp210Function
    {
        get { return (Func<CP210UserControl, CP210x_STATUS>)GetValue(cp210FunctionProperty); }
        set { SetValue(cp210FunctionProperty, value); }
    }

    public static readonly DependencyProperty cp210FunctionProperty = DependencyProperty.Register("cp210Function", typeof(Func<CP210UserControl, CP210x_STATUS>), typeof(CP210UserControl), new PropertyMetadata(null));

but when I run the program I get this error

A first chance exception of type 'System.Windows.Markup.XamlParseException' occurred in PresentationFramework.dll
Additional information: 'Func`2' type does not have a public TypeConverter class.  Error at Line 7 Position 122.

So how can I make said TypeConverter? is it even possible?

EDIT

here is what basically my end game.

What I am shooting for

Robert Snyder
  • 2,399
  • 4
  • 33
  • 65
  • 1
    Too much code where it does not belong. Create a proper ViewModel and all your problems will be magically solved. The UI is not the right place to put your interaction with whatever external library. – Federico Berasategui Oct 17 '13 at 18:05
  • @HighCore Well i'm not using the MVVM pattern for this program. I'm not well versed in it enough to know how to use it as quickly as throwing together this window. It may not be the nicest thing I've written, but I have tried MVVM and my code was much worse (i'm betting that is my fault). The program is still small enough where changing to a MVVM wouldn't be horrible, but I'd need some direction on how to do this. – Robert Snyder Oct 17 '13 at 18:14
  • simple: put all your code in a class called `MyViewModel` (which has nothing to do with WPF, is just a simple regular class) and expose simple properties (`string`, `int`, etc) that the UI elements will Bind to. – Federico Berasategui Oct 17 '13 at 18:16
  • my user control has a button that fires uses the Func. That is what would hold me back. plus it would seem that I would have to make a million properties that all get bound too, just doesn't seem very condensed. – Robert Snyder Oct 17 '13 at 18:29
  • it doesn't make sense. a `Func` needs an input parameter. A "Button" does not represent an action that would require an input parameter. And you're already creating a million properties in the UI instead of putting these in the VM. I don't see how that is a benefit. Post a screenshot of what you need and I can tell you the right way to do it in WPF. – Federico Berasategui Oct 17 '13 at 18:33
  • ok, will do here in a few minutes, I have a few fires I have to put out first here. Thank you for your insight in this. – Robert Snyder Oct 17 '13 at 18:45

1 Answers1

1

Your class structure seems really awkward to me.

The UI is not the right place to put any interactions with any external library.

If you're working with WPF, you really need to embrace The WPF Mentality.

This is how I'd do that in WPF:

<UserControl x:Class="SiliconLabsRosettaStone.CP210UserControl"
             ...>
    <!-- ... Omitted for brevity -->

            <TextBlock Text="{Binding Caption}" ... />

            <TextBlock Text="{Binding NumberOfDevices}" />

            <Button Content="Execute" Click="Button_Click" ... />

    <!-- ... -->
</UserControl>

User Control Code Behind:

public class CP210UserControl: UserControl
{
   public CP210UserControl()
   {
       InitializeComponent();
   }

   private void Button_Click(object sender, RoutedEventArgs e)
   {
      var VM = DataContext as MyViewModel;
      if (VM != null)
      {
          //Correct uses of Code-Behind:

          //1 - Call ViewModel Methods:
          var response = VM.GetNumberDevices();

          //2 - UI-specific code which does not contain business logic:
          responseTextBlock.Background = response ? null : Brushes.Pink;
      }
   }
}

Window:

<Window ....>
   <local:CP210UserControl/>
</Window>

Code Behind:

public class MainWindow: Window
{
    public MainWindow()
    {
       InitializeComponent();
       DataContext = new MyViewModel();
    }
}

ViewModel:

public class MyViewModel: INotifyPropertyChanged
{
   public string Caption {get;set;} //NotifyPropertyChanged() required.

   public string NumberOfDevices {get;set;} //NotifyPropertyChanged() required.

   public bool GetNumberDevices()
   {
       int devices = 0;
       var result = CP210xWrapper.CP210x_GetNumDevices(ref devices);
       NumberOfDevices = string.Format("Number Of Devices:{0}", devices);

       return result == CP210x_STATUS.CP210x_SUCCESS;
   }

   //INotifyPropertyChanged implementation omitted for brevity.
}
  • No bizarre Func is required.
  • No DependencyProperties are required.
  • Just simple, simple Properties and INotifyPropertyChanged. That's how you code in WPF.
  • It's perfectly valid to use code behind this way, just to call methods in the ViewModel, instead of putting the actual logic and behavior in code behind.
  • WPF Rocks
  • Let me know if you need further help.
Community
  • 1
  • 1
Federico Berasategui
  • 43,562
  • 11
  • 100
  • 154
  • So the SDK has 39 methods in it, the one that I posted is just the easiest one. Granted not all of them would be implemented such as all of the SetXXXXX() but all of the Get methods I would be curious about. Do I still follow this example? for example... this `public static extern CP210x_STATUS CP210x_GetProductString(int dwDeviceNum, IntPtr lpvDeviceString, int dwFlags );` – Robert Snyder Oct 17 '13 at 19:08
  • @RobertSnyder see my last edit. I removed the reference from external library types from the UI layer completely, by simply returning a `bool` value from the ViewModel. Think of the VM as the intermediary between the UI and the application logic. – Federico Berasategui Oct 17 '13 at 19:11
  • @RobertSnyder Seems to me that would be an `ItemsControl` where each item is a `MyViewModel` (or a subclass of it), and the `ItemTemplate` is your `UserControl`. – Federico Berasategui Oct 17 '13 at 19:16
  • @RobertSnyder go ahead and try to implement a single one, and when you get it, post another question so I can tell you how to use the `ItemsControl` with that. – Federico Berasategui Oct 17 '13 at 19:18