5

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.

Anatoliy Nikolaev
  • 22,370
  • 15
  • 69
  • 68
D_D_S_B
  • 65
  • 1
  • 6
  • Post your current code and XAML. – Federico Berasategui Oct 16 '13 at 03:01
  • I don't think that will help, its over 6 thousand lines consisting of 10 different usercontrols and multiple classes similar to a MVVM pattern, and currently a total mess since I've been trying a hundred and one different thing to make this work right. My program has many more pixel generators / displays, I just wanted to keep the question simple/r. If I can figure out how to do this correctly with one usercontrol the rest will follow. – D_D_S_B Oct 16 '13 at 03:06
  • Ok. **You can't manipulate the UI in any other thread, only in the UI thread**. That's what I can say without seeing any code, or at least a sample of what you're trying to achieve and what is the data that you're generating. – Federico Berasategui Oct 16 '13 at 03:09
  • Ok Ill add in an example to the question: – D_D_S_B Oct 16 '13 at 03:16
  • dude, what in the world is that? can you post a screenshot of what you're trying to achieve? – Federico Berasategui Oct 16 '13 at 03:26
  • That one in particular isn't really anything at all right now, I do have a pic from an older project written in windows forms that I am currently re-writing in WPF but I don't want to go any further until I figure this out. – D_D_S_B Oct 16 '13 at 03:28
  • Actually I do have a photo if this from a few days ago, but how do I post a photo? – D_D_S_B Oct 16 '13 at 03:29
  • use `imgur.com` and post a link to the picture in your question. Then I can edit the question and embed the picture. – Federico Berasategui Oct 16 '13 at 03:35
  • http://i.imgur.com/crLeemH.png?1 – D_D_S_B Oct 16 '13 at 03:38
  • What you do is create a thread to create the image, then you call the dispatcher of the UI to update the UI when a new frame ("image") is available. – ps2goat Oct 16 '13 at 03:41
  • The one I am referring to is the usercontrol on the left. – D_D_S_B Oct 16 '13 at 03:45

1 Answers1

10

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.
Community
  • 1
  • 1
Federico Berasategui
  • 43,562
  • 11
  • 100
  • 154
  • You are the man, thank you. I wish I could code as fast as you... :/ – D_D_S_B Oct 16 '13 at 04:07
  • @D_D_S_B It's not a matter of "fast", but rather "concise". See how I have written much less code, and a big part of it is just boilerplate (generate random colors). That's what WPF enables, because of it's databinding capabilities. – Federico Berasategui Oct 16 '13 at 04:09
  • I am still in the process of learning and trying to wrap my head around MVVM, mainly its just a few components I have to get familiar with. This is great and has exactly to do with what I am going for. Thanks again! – D_D_S_B Oct 16 '13 at 04:12