Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to create a usercontrol on different thread?

I am creating a UI that will be displaying generated patterns similar to fractals and cellular automation, but they will be continuously generating and automated.

enter image description here

The pixels and pixel colors will be displayed as a grid of squares in a usercontrol. I've already created the usercontrol to display this but since it is constantly calculation at every timer.tick it dramatically slows down the rest of the UI and causes all other elements to stutter.

So I looked into threading and set the "calculating" part in a BackgroundWorker DoWork(), which ultimately didn't end up working out the way I wanted it to. The BackgroundWorker is using data from the main thread (Rectangle[400]), so I had to use Dispatcher.Invoke(new Action(() => { })); which simply defeats the purpose, the program still ran very "stuttery".

So, how can I create a usercontrol...name:display_control entirely on a separate thread and have it appear in another usercontrol...name:unsercontrol1 (Which is running in the main thread), ? Then I could possibly databind the user_input data with the usercontrol1 User_Input_Class instance.

Or, is there a better way to achieve this? Only other way I can think of doing this is to simply create an entirely separate program for the display and share a file containing the user_input data which is very unprofessional.

public partial class Fractal_Gen_A : UserControl
{
    byte W_R = 0;
    byte W_G = 255;
    byte W_B = 0;

    int Pixel_Max_Width = 20;
    int Pixel_Max_Height = 20;

    Color[] Pixel_Color = new Color[20 * 20]; //Width_Max * Canvas_Height_Count
    Rectangle[] Pixel = new Rectangle[20 * 20];
    Color[] Temp_Color = new Color[20 * 20];

    BackgroundWorker worker = new BackgroundWorker();
    private void Timer_Tick(object sender, EventArgs e)
    {
        try { worker.RunWorkerAsync(); }
        catch {}

    }

    Function_Classes.Main_Binder.FGA LB = new Function_Classes.Main_Binder.FGA(); //LB = local Binder
    DispatcherTimer Timer = new DispatcherTimer();

    public Fractal_Gen_A()
    {   
        LB.Brush = new SolidColorBrush[Pixel_Max_Width * Pixel_Max_Height];
        InitializeComponent();
        DataContext = LB;

        Set_Up_Binded_Brushes();
        worker.DoWork += Worker_Work;

        Timer.Tick += new EventHandler(Timer_Tick);
        Timer.Interval = new TimeSpan(0, 0, 0, 0, 300);            
    }             

    private void Set_Up_Binded_Brushes()
    {            
        for (int i = 0; i < Pixel_Max_Width *Pixel_Max_Height; i++)
        {
            LB.Brush[i] = new SolidColorBrush(Color.FromRgb(255, 0, 0));
        }
    }

    private delegate void UpdateMyDelegatedelegate(int i);
    private void UpdateMyDelegateLabel(int i){}

    private void Worker_Work(object sender, DoWorkEventArgs e)
    {
            try
            {
                BackgroundWorker Worker = sender as BackgroundWorker;
                SolidColorBrush[] Temp_Brush = new SolidColorBrush[Pixel_Max_Height * Pixel_Max_Width];                    

                for (int h = 0; h < Pixel_Max_Height - 1; h++)
                {
                    for (int w = 0; w < Pixel_Max_Width; w++)
                    {                            
                       Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal, new Action(() => { 
                            Temp_Brush[((h + 1) * Pixel_Max_Width) + w] = new SolidColorBrush();
                            Temp_Brush[((h + 1) * Pixel_Max_Width) + w].Color = LB.Brush[(h * Pixel_Max_Width) + w].Color; 
                        }));
                    }
                } 

                W_R += 23;
                for (int w = 0; w < Pixel_Max_Width; w++)
                {
                    W_B += 23 % 255;
                    W_R += 23 % 255;

                    Temp_Brush[w].Color = Color.FromRgb(W_R, W_B, W_G);                        
                }

                 Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal, new Action(() => {
                    Array.Copy(Temp_Brush, 0, LB.Brush, 0, Pixel_Max_Height * Pixel_Max_Width);
                 }));

                UpdateMyDelegatedelegate UpdateMyDelegate = new UpdateMyDelegatedelegate(UpdateMyDelegateLabel);
            }

            catch { }           
    }

    private void Worker_Done(object sender, RunWorkerCompletedEventArgs e)
    {

    }

    private void UserControl_Loaded(object sender, RoutedEventArgs e)
    {            
        double X_Set = Pixel_Canvas.ActualWidth / Pixel_Max_Width;
        double Y_Set = Pixel_Canvas.ActualHeight / Pixel_Max_Height;

        for (int h = 0; h < Pixel_Max_Height; h++)
        {
            for (int w = 0; w < Pixel_Max_Width; w++)
            {
                    Pixel_Color[(h * Pixel_Max_Width) + w] = Color.FromRgb(255, 0, 0);
                    Pixel[(h * Pixel_Max_Width) + w] = new Rectangle();
                    Pixel[(h * Pixel_Max_Width) + w].Width = Pixel_Canvas.ActualWidth / Pixel_Max_Width;
                    Pixel[(h * Pixel_Max_Width) + w].Height = Pixel_Canvas.ActualHeight / Pixel_Max_Height;
                    Pixel[(h * Pixel_Max_Width) + w].Stroke = new SolidColorBrush(Color.FromRgb(100, 100, 100)); //Pixel_Color[(h * Pixel_Max_Width) + w] 
                    Pixel[(h * Pixel_Max_Width) + w].StrokeThickness = .5;    
                    Pixel[(h * Pixel_Max_Width) + w].Fill = new SolidColorBrush(Color.FromRgb(255, 0, 0)); //Pixel_Color[(h * Pixel_Max_Width) + w]
                    Canvas.SetLeft(Pixel[(h * Pixel_Max_Width) + w], (X_Set * w) + 0);
                    Canvas.SetTop(Pixel[(h * Pixel_Max_Height) + w], (Y_Set * h) + 0);
                    Pixel_Canvas.Children.Add(Pixel[(h * Pixel_Max_Height) + w]);

                    int Temp_Count = (h * Pixel_Max_Width) + w;
                   string Temp_Bind = "Brush[" + Temp_Count.ToString() + "]";
                   Binding Bind = new Binding(Temp_Bind);
                   Pixel[(h * Pixel_Max_Height) + w].SetBinding(Rectangle.FillProperty, Bind ); 

                // Dispatcher.Invoke(new Action(() => { }));
                   Timer.Start();
            }
        }
        Timer.Start();
    }

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        Window pw = new PopUp_Window();            
        pw.Show();            
    }
}

Basically, I am using usercontrols to act as views, 2 will be displayed at all times, one on the left one on the right.

like image 666
D_D_S_B Avatar asked Oct 16 '13 02:10

D_D_S_B


1 Answers

Ok. Delete all your code and start all over.

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

This is how you do that in WPF:

<Window x:Class="MiscSamples.Fractals"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Fractals" Height="300" Width="300">
    <ItemsControl ItemsSource="{Binding Cells}">
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <UniformGrid Rows="{Binding Size}" Columns="{Binding Size}"/>            
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>

        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <Border BorderBrush="Gray" BorderThickness="2">
                    <Border.Background>
                        <SolidColorBrush Color="{Binding Color,Mode=OneTime}"/>
                    </Border.Background>
                </Border>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>
</Window>

Code Behind:

public partial class Fractals : Window
{
    public Fractals()
    {
        InitializeComponent();
        DataContext = new FractalViewModel();
    }
}

ViewModel:

public class FractalViewModel:PropertyChangedBase
{
    private ObservableCollection<FractalCell> _cells;
    public int Rows { get; set; }

    public int Columns { get; set; }

    public ObservableCollection<FractalCell> Cells
    {
        get { return _cells; }
        set
        {
            _cells = value;
            OnPropertyChanged("Cells");
        }
    }

    public FractalViewModel()
    {
        var ctx = TaskScheduler.FromCurrentSynchronizationContext();

        Task.Factory.StartNew(() => CreateFractalCellsAsync())
                    .ContinueWith(x => Cells = new ObservableCollection<FractalCell>(x.Result), ctx);
    }

    private List<FractalCell> CreateFractalCellsAsync()
    {
        var cells = new List<FractalCell>();
        var colors = typeof(Colors).GetProperties().Select(x => (Color)x.GetValue(null, null)).ToList();
        var random = new Random();

        for (int i = 0; i < 32; i++)
        {
            for (int j = 0; j < 32; j++)
            {
                cells.Add(new FractalCell() { Row = i, Column = j, Color = colors[random.Next(0, colors.Count)] });
            }
        }

        return cells;
    }
}

Data Item:

public class FractalCell:PropertyChangedBase
{
    public int Row { get; set; }

    public int Column { get; set; }

    public Color Color { get; set; }
}

PropertyChangedBase class:

public class PropertyChangedBase:INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged(string propertyName)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null) 
            handler(this, new PropertyChangedEventArgs(propertyName));
    }
}

Result:

enter image description here

  • Notice how I'm not manipulating any UI elements in procedural code. Everything is done with simple, simple Properties and INotifyPropertyChanged. That's how you program in WPF.
  • I'm using an ItemsControl with a UniformGrid and a simple DataTemplate for the cells.
  • The example is generating random colors, but you can get this from whatever data source you like. Notice that the data is completely decoupled from the UI and thus it makes it much easier for you to manipulate your own, simple classes rather than the complex and arcane WPF object model.
  • It also makes it easier for you to implement a multi-threaded scenario because, again, you are not dealing with the UI, but rather with Data. Remember that the UI can only be manipulated in the UI Thread.
  • WPF Rocks. Just copy and paste my code in a File -> New Project -> WPF Application and see the results for yourself.
  • Let me know if you need further help.
like image 130
Federico Berasategui Avatar answered Nov 03 '22 01:11

Federico Berasategui